skip to content
==> 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}