==> tests/edit.c <==1/* fleet.rs: changes_edit_the_document, a_lockstep_upgrade_must_run_on_every_member,
2 * a_revert_undoes_only_its_change. */
3#include "mesh/check.h"
4#include "mesh/fleet_edit.h"
5#include "mesh/mt.h"
6#include "mesh/abi/placement.h"
7
8#include <stdlib.h>
9
10static mfe_image image(const char *dir, const char *name, uint64_t hash, bool lockstep, const char *b0, const char *b1, bool autostart) {
11 mfe_image im = {.dir = mt_strdup(dir), .name = mt_strdup(name), .hash = hash, .lockstep = lockstep, .autostart = autostart, .needs = msn_default()};
12 mt_push(&im.backends, b0);
13 if (b1) mt_push(&im.backends, b1);
14 return im;
15}
16
17static bool cat(void *ctx, const char *dir, mfe_image *out, char **err) {
18 (void)ctx;
19 if (!strcmp(dir, "hello") || !strcmp(dir, "hello-b")) *out = image(dir, "hello", !strcmp(dir, "hello") ? 1 : 2, false, "cuda", NULL, true);
20 else if (!strcmp(dir, "train") || !strcmp(dir, "train-b")) *out = image(dir, "train", !strcmp(dir, "train") ? 10 : 11, true, "cuda", "metal", true);
21 else if (!strcmp(dir, "doom")) {
22 *out = image(dir, "doom", 20, false, "cuda", NULL, false);
23 out->placeable = true;
24 out->place_cap = MESH_PLACE_CAP_SPLIT;
25 out->place_fanout = 4;
26 } else {
27 *err = mt_fmt("manifest: %s: no such dir", dir);
28 return false;
29 }
30 return true;
31}
32
33static ml_list up013(void *ctx, const mt_strs *b) { return (void)ctx, (void)b, ml_of((uint32_t[]){0, 1, 3}, 3); }
34
35static mfe_ctx cx = {cat, up013, NULL, 4};
36
37/* edit; the document, or NULL with *err. */
38static mfd_doc *edit(const mfd_doc *d, const char *line, char **err) {
39 mfe_req r;
40 if (!mfe_parse(line, &r, err)) return NULL;
41 mfd_doc *out = malloc(sizeof *out);
42 char *what = NULL;
43 bool ok = mfe_edit(d, &r, &cx, out, &what, err);
44 mfe_req_free(&r);
45 free(what);
46 if (!ok) {
47 free(out);
48 return NULL;
49 }
50 return out;
51}
52
53static void drop(mfd_doc *d) {
54 mfd_doc_free(d);
55 free(d);
56}
57
58static bool fails(const mfd_doc *d, const char *line, const char *needle) {
59 char *err = NULL;
60 mfd_doc *x = edit(d, line, &err);
61 if (x) {
62 drop(x);
63 return false;
64 }
65 bool ok = !needle || mt_contains(err, needle);
66 if (!ok) fprintf(stderr, "error was: %s\n", err);
67 free(err);
68 return ok;
69}
70
71static void changes(void) {
72 char *err = NULL;
73 mfd_doc d0 = {0};
74 mfe_req r;
75 CHECK(mfe_parse("load hello", &r, &err));
76 mfd_doc d1;
77 char *what = NULL;
78 CHECK(mfe_edit(&d0, &r, &cx, &d1, &what, &err));
79 CHECK(mt_contains(what, "load hello") && mfd_prog_get(&d1, "hello")->run && mfd_prog_get(&d1, "hello")->hash == 1);
80 free(what);
81 mfe_req_free(&r);
82 CHECK(fails(&d1, "load hello-b", "name exists"));
83 CHECK(fails(&d1, "load nope", "manifest"));
84 mfd_doc *d2 = edit(&d1, "upgrade hello hello-b", &err);
85 mfd_prog *h = mfd_prog_get(d2, "hello");
86 CHECK(h->hash == 2 && h->has_prev && !strcmp(h->prev_dir, "hello") && h->prev_hash == 1);
87 mfd_doc *d3 = edit(d2, "rollback hello", &err);
88 CHECK(mfd_prog_get(d3, "hello")->hash == 1 && !strcmp(mfd_prog_get(d3, "hello")->dir, "hello"));
89 CHECK(fails(&d1, "rollback hello", "no previous"));
90 mfd_doc *d4 = edit(d3, "load train", &err);
91 mfd_group *g = mfd_group_of(d4, "train");
92 CHECK(g && g->members.n == 3 && g->members.v[2] == 3 && g->epoch == 1 && g->state == MFD_RUNNING);
93 mfd_doc *d5 = edit(d4, "upgrade train train-b", &err);
94 CHECK(d5->groups[0].epoch == 1 && d5->groups[0].cycle == 1 && d5->groups[0].op.kind == MFD_OP_UPGRADE);
95 mfd_doc *d6 = edit(d5, "task train 3 1000593162", &err);
96 CHECK(d6->groups[0].cycle == 2 && d6->groups[0].op.kind == MFD_OP_TASK && d6->groups[0].op.op == 3 && d6->groups[0].op.arg == 1000593162);
97 mfd_doc *d7 = edit(d6, "reset train", &err);
98 CHECK(d7->groups[0].epoch == 2 && d7->groups[0].cycle == 0);
99 mfd_doc p = mfd_doc_clone(d7);
100 p.groups[0].state = MFD_PAUSED;
101 CHECK(fails(&p, "task train 1 2", "paused"));
102 CHECK(fails(d7, "task hello 1 2", NULL));
103 mfd_doc *d8 = edit(&p, "group train reform", &err);
104 CHECK(d8->groups[0].epoch == 3 && d8->groups[0].state == MFD_RUNNING);
105 mfd_doc *d9 = edit(d8, "group train on_loss reform 5000", &err);
106 CHECK(d9->groups[0].on_loss == MFD_LOSS_REFORM && d9->groups[0].auto_ms == 5000);
107 mfd_doc *d10 = edit(d9, "load doom", &err);
108 mfd_doc *d11 = edit(d10, "place doom split auto", &err);
109 const mt_strs *pw = mfd_place_get(d11, "doom");
110 CHECK(pw && pw->n == 2 && !strcmp(pw->v[0], "split") && !strcmp(pw->v[1], "auto"));
111 CHECK(fails(d11, "place doom pin 9", NULL));
112 CHECK(fails(d11, "place hello pin 1", "no `place`"));
113 mfd_doc *d12 = edit(d11, "place doom local", &err);
114 CHECK(!mfd_place_get(d12, "doom"));
115 mfd_doc *d13 = edit(d11, "unload train", &err);
116 CHECK(d13->ngroups == 0 && !mfd_prog_get(d13, "train"));
117 mfd_doc *d14 = edit(d11, "unload doom", &err);
118 CHECK(d14->nplaces == 0);
119 CHECK(!mfe_parse("frobnicate x", &r, &err));
120 CHECK_S(err, "not a change: \"frobnicate x\"");
121 free(err);
122 CHECK(!mfe_parse("task train x 1", &r, &err));
123 free(err);
124 /* The scheduler's changes. */
125 mfd_doc *p1 = edit(d11, "sched pin hello 1,3", &err);
126 mfd_prog *hp = mfd_prog_get(p1, "hello");
127 CHECK(hp->pin.some && hp->pin.l.n == 2 && hp->pin.l.v[1] == 3);
128 CHECK(fails(d11, "sched pin hello 7", NULL) && fails(d11, "sched pin train 1", "lockstep"));
129 mfd_doc *p1b = edit(p1, "sched unpin hello", &err);
130 CHECK(!mfd_prog_get(p1b, "hello")->pin.some);
131 mfd_doc *p2 = edit(d11, "sched pause", &err);
132 mfd_doc *p2b = edit(p2, "sched resume", &err);
133 CHECK(p2->sched_paused && !p2b->sched_paused);
134 mfd_doc *p3 = edit(d11, "sched autoreform on", &err);
135 CHECK(p3->autoreform);
136 mfd_doc *p4 = edit(d11, "sched cordon 3", &err);
137 CHECK_U(p4->cordon, 8);
138 CHECK(!mfe_parse("sched cordon 9", &r, &err));
139 CHECK_S(err, "\"9\": a member");
140 free(err);
141 CHECK(!mfe_parse("sched pin x", &r, &err));
142 free(err);
143 CHECK(mfe_parse("unload hello --now", &r, &err) && r.kind == MFE_UNLOAD && !strcmp(r.name, "hello"));
144 mfe_req_free(&r);
145 CHECK(mfe_parse("task t 0x10 7", &r, &err) && r.op == 16 && r.arg == 7);
146 mfe_req_free(&r);
147 mfd_doc_free(&d1);
148 mfd_doc_free(&p);
149 drop(d2), drop(d3), drop(d4), drop(d5), drop(d6), drop(d7), drop(d8), drop(d9), drop(d10), drop(d11), drop(d12), drop(d13), drop(d14);
150 drop(p1), drop(p1b), drop(p2), drop(p2b), drop(p3), drop(p4);
151}
152
153static bool cat2(void *ctx, const char *dir, mfe_image *out, char **err) {
154 (void)ctx, (void)err;
155 *out = image(dir, "train", strlen(dir), true, "cuda", strcmp(dir, "train-c") ? "metal" : NULL, true);
156 return true;
157}
158
159/* System 3 is the Metal member. */
160static ml_list up_metal(void *ctx, const mt_strs *b) {
161 (void)ctx;
162 ml_list v = ml_of((uint32_t[]){0, 1}, 2);
163 if (mt_strs_has(b, "metal")) ml_push(&v, 3);
164 return v;
165}
166
167static void lockstep_upgrade(void) {
168 mfe_ctx c2 = {cat2, up_metal, NULL, 4};
169 mfe_req r;
170 char *err = NULL, *what = NULL;
171 mfd_doc z = {0}, d, x;
172 mfe_parse("load train", &r, &err);
173 CHECK(mfe_edit(&z, &r, &c2, &d, &what, &err));
174 free(what);
175 mfe_req_free(&r);
176 CHECK(d.groups[0].members.n == 3);
177 mfe_parse("upgrade train train-c", &r, &err);
178 CHECK(!mfe_edit(&d, &r, &c2, &x, &what, &err));
179 CHECK(mt_starts(err, "unsupported") && mt_contains(err, "member(s) 3"));
180 free(err);
181 mfe_req uq;
182 mfe_parse("upgrade train train-b", &uq, &err);
183 CHECK(mfe_edit(&d, &uq, &c2, &x, &what, &err));
184 free(what);
185 mfd_doc_free(&x);
186 mfe_req_free(&uq);
187 /* Without system 3 in the group: allowed. */
188 mfd_doc d2 = mfd_doc_clone(&d);
189 ml_free(&d2.groups[0].members);
190 d2.groups[0].members = ml_of((uint32_t[]){0, 1}, 2);
191 CHECK(mfe_edit(&d2, &r, &c2, &x, &what, &err));
192 free(what);
193 mfd_doc_free(&x);
194 mfe_req_free(&r);
195 mfd_doc_free(&d);
196 mfd_doc_free(&d2);
197}
198
199static mfd_doc base_doc(void) {
200 mfd_doc d = {.version = 7, .term = 3};
201 mesh_needs n = msn_default();
202 mfd_prog h = mfd_prog_new("hello", 0xabc, true, &n);
203 h.reset = 2;
204 mfd_prog_put(&d, "hello", h);
205 mfd_prog t = mfd_prog_new("train-b", 0x123, true, &n);
206 t.lockstep = true;
207 mfd_prog_put(&d, "train", t);
208 mesh_needs_free(&n);
209 mfd_group_push(&d, (mfd_group){.name = mt_strdup("train"), .program = mt_strdup("train"), .epoch = 4, .cycle = 2, .members = ml_of((uint32_t[]){0, 2, 3}, 3),
210 .state = MFD_RUNNING, .on_loss = MFD_LOSS_REFORM, .auto_ms = 5000});
211 mt_strs w = {0};
212 mt_push(&w, "split");
213 mt_push(&w, "auto");
214 mfd_place_put(&d, "doom", w);
215 return d;
216}
217
218static void revert_only_its_change(void) {
219 mfd_doc base = base_doc();
220 mfd_doc bad = mfd_doc_clone(&base);
221 mfd_prog_get(&bad, "hello")->hash = 0xbad;
222 mfd_prog_put(&bad, "new", mfd_prog_clone(mfd_prog_get(&bad, "hello")));
223 mfd_doc cur = mfd_doc_clone(&bad);
224 cur.groups[0].state = MFD_PAUSED;
225 mt_strs w = {0};
226 mt_push(&w, "split");
227 mfd_place_put(&cur, "hello", w);
228 cur.version = 12;
229 mfd_doc r = mfe_revert(&cur, &base, &bad);
230 CHECK(r.nprogs == base.nprogs);
231 for (size_t i = 0; i < r.nprogs && i < base.nprogs; i++) CHECK(!strcmp(r.pname[i], base.pname[i]) && mfd_prog_eq(&r.prog[i], &base.prog[i]));
232 CHECK(r.groups[0].state == MFD_PAUSED && mfd_place_get(&r, "hello") && r.version == 12);
233 mfd_doc r2 = mfe_revert(&bad, &base, &bad);
234 CHECK(mfd_doc_eq(&r2, &base));
235 mfd_doc_free(&r), mfd_doc_free(&r2), mfd_doc_free(&cur), mfd_doc_free(&bad), mfd_doc_free(&base);
236}
237
238/* sched_tests.rs a_pin_never_splits_a_colocated_pair, the control port's half: `sched pin` of a
239 * program colocated with a loaded anchor is refused, pointing at the anchor. */
240static void pin_refuses_colocated(void) {
241 mfd_doc d = {.version = 1, .term = 1};
242 mesh_needs n = msn_default();
243 mfd_prog_put(&d, "pipehash", mfd_prog_new("x", 1, true, &n));
244 n.colocate_with = mt_strdup("pipehash");
245 mfd_prog_put(&d, "pipesrc", mfd_prog_new("x", 1, true, &n));
246 mesh_needs_free(&n);
247 char *err = NULL;
248 CHECK(fails(&d, "sched pin pipesrc 1", "colocated with pipehash"));
249 mfd_doc *ok = edit(&d, "sched pin pipehash 1", &err);
250 CHECK(ok != NULL);
251 if (ok) drop(ok);
252 mfd_doc_free(&d);
253}
254
255int main(void) {
256 pin_refuses_colocated();
257 changes();
258 lockstep_upgrade();
259 revert_only_its_change();
260 return mesh_check_done();
261}