skip to content
==> tests/placer.c <==
1/* place.rs Placer tests: change_protocol_moves_state_across_three, split_auto_uses_measured_times,
2 * change_times_out_and_rolls_back, split_frames_assemble_on_every_machine,
3 * follower_keeps_the_newest_and_rejects_strays, member_loss_replans_and_return_rejoins. */
4#include "mesh/abi/peer_frames.h"
5#include "mesh/abi/place_ops.h"
6#include "mesh/check.h"
7#include "mesh/placer.h"
8
9#include <stdlib.h>
10
11static mps_spec spec(void) {
12    mps_region st[] = {{0x0, 0x8}, {0x100, 0x4}}, out[] = {{0x1000, 0x40}, {0x1400, 0x40}};
13    return mps_new(MESH_PLACE_CAP_SPLIT, st, 2, 0x4, out, 2, 0x2000, 4);
14}
15
16static void viewfn(void *ctx, uint32_t o, uint32_t n, uint8_t *out) {
17    uint8_t salt = (uint8_t)(uintptr_t)ctx;
18    for (uint32_t i = 0; i < n; i++) out[i] = (uint8_t)(o + i) ^ salt;
19}
20
21static mpw_bytes read_state(uint8_t salt) {
22    mps_spec s = spec();
23    mpw_bytes b = mpc_read_state(&s, viewfn, (void *)(uintptr_t)salt);
24    mps_free(&s);
25    return b;
26}
27
28static mpw_bytes gather(uint32_t mask, uint8_t salt) {
29    mps_spec s = spec();
30    mpw_bytes b = mpc_gather(&s, mask, viewfn, (void *)(uintptr_t)salt);
31    mps_free(&s);
32    return b;
33}
34
35typedef struct {
36    uint8_t salt;
37} statectx;
38
39static mpw_bytes state_cb(void *ctx) { return read_state(((statectx *)ctx)->salt); }
40
41static mpw_place place_msg(const mpw_bytes *b) {
42    size_t n = 0;
43    char *err = NULL;
44    CHECK(b->b[0] == MESH_F_PLACE && mpw_place_rest_len(b->b + 1, &n, &err));
45    mpw_place m = {0};
46    CHECK(mpw_place_decode(b->b + 1, b->b + 1 + MPW_PLACE_HEAD, n, &m, &err));
47    return m;
48}
49
50static mpw_frame frame_msg(const mpw_bytes *b) {
51    size_t n = 0;
52    char *err = NULL;
53    CHECK(b->b[0] == MESH_F_FRAME && mpw_frame_rest_len(b->b + 1, &n, &err));
54    mpw_frame m = {0};
55    CHECK(mpw_frame_decode(b->b + 1, b->b + 1 + MPW_FRAME_HEAD, n, &m, &err));
56    return m;
57}
58
59/* (from, act) */
60typedef struct {
61    uint32_t k;
62    mpl_act a;
63} logged;
64
65typedef struct {
66    logged *v;
67    size_t n;
68} logv;
69
70static void log_push(logv *l, uint32_t k, mpl_act a) {
71    l->v = realloc(l->v, (l->n + 1) * sizeof *l->v);
72    l->v[l->n++] = (logged){k, a};
73}
74
75static void log_free(logv *l) {
76    for (size_t i = 0; i < l->n; i++) {
77        mpw_bytes_free(&l->v[i].a.bytes);
78        free(l->v[i].a.text);
79    }
80    free(l->v);
81    *l = (logv){0};
82}
83
84/* Takes the acts into the log (as from k). */
85static void take(logv *l, uint32_t k, mpl_acts *a) {
86    for (size_t i = 0; i < a->n; i++) log_push(l, k, a->v[i]);
87    free(a->v);
88    *a = (mpl_acts){0};
89}
90
91/* A cluster of placers: routes their sends (by dest) until quiet, ticking each (drained) after every
92 * pass; logs (from, act). */
93static void pump(mpl_placer **ps, size_t np, logv *out, uint64_t t, logv *log) {
94    for (int passes = 1;; passes++) {
95        CHECK(passes < 50);
96        logv next = {0};
97        for (size_t i = 0; i < out->n; i++) {
98            uint32_t from = out->v[i].k;
99            mpl_act *act = &out->v[i].a;
100            if (act->kind == MPL_SEND)
101                for (uint32_t k = 0; k < np; k++) {
102                    bool to = act->dest.to_mask ? (k != from && (act->dest.k >> k & 1)) : k == act->dest.k;
103                    if (!to) continue;
104                    mpl_acts r = act->bytes.b[0] == MESH_F_PLACE ? mpl_peer_place(ps[k], place_msg(&act->bytes), from, t) : mpl_peer_frame(ps[k], frame_msg(&act->bytes), t);
105                    take(&next, k, &r);
106                }
107            log_push(log, from, *act);
108        }
109        free(out->v);
110        *out = (logv){0};
111        for (uint32_t k = 0; k < np; k++) {
112            statectx c = {(uint8_t)(k + 1)};
113            mpl_acts r = mpl_tick(ps[k], t, true, state_cb, &c);
114            take(&next, k, &r);
115        }
116        if (!next.n) {
117            free(next.v);
118            break;
119        }
120        *out = next;
121    }
122}
123
124static mt_strs w(const char *s) { return mt_words(s); }
125
126static mpl_placer **cluster(uint32_t n, uint64_t t) {
127    mpl_placer **ps = calloc(n, sizeof *ps);
128    for (uint32_t k = 0; k < n; k++) {
129        ps[k] = mpl_new("doom", spec(), k, n);
130        mpl_acts a = mpl_view(ps[k], mpm_bits(n), true, 0, NULL, t);
131        mpl_acts_free(&a);
132    }
133    return ps;
134}
135
136static void free_cluster(mpl_placer **ps, uint32_t n) {
137    for (uint32_t k = 0; k < n; k++) mpl_free(ps[k]);
138    free(ps);
139}
140
141static void run(mpl_placer **ps, size_t np, uint32_t from, mpl_acts *acts, uint64_t t, logv *log) {
142    logv out = {0};
143    take(&out, from, acts);
144    pump(ps, np, &out, t, log);
145}
146
147/* The answers in a log (handle, ok, text). */
148static const mpl_act *answer_in(const logv *l, size_t i) {
149    size_t c = 0;
150    for (size_t j = 0; j < l->n; j++)
151        if (l->v[j].a.kind == MPL_ANSWER && c++ == i) return &l->v[j].a;
152    return NULL;
153}
154
155static size_t answers(const logv *l) {
156    size_t c = 0;
157    for (size_t j = 0; j < l->n; j++) c += l->v[j].a.kind == MPL_ANSWER;
158    return c;
159}
160
161static const mpl_act *answer_of(const mpl_acts *a) {
162    for (size_t i = 0; i < a->n; i++)
163        if (a->v[i].kind == MPL_ANSWER) return &a->v[i];
164    return NULL;
165}
166
167static size_t states_written(const logv *l, uint32_t k, mpw_bytes *first) {
168    size_t c = 0;
169    for (size_t j = 0; j < l->n; j++)
170        if (l->v[j].k == k && l->v[j].a.kind == MPL_WRITE_STATE) {
171            if (!c && first) *first = l->v[j].a.bytes;
172            c++;
173        }
174    return c;
175}
176
177static bool bytes_eq(mpw_bytes a, mpw_bytes b) { return a.n == b.n && (!a.n || !memcmp(a.b, b.b, a.n)); }
178
179static mpm_mode split4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { return mpm_split(mpm_assign_new((uint32_t[]){a, b, c, d}, 4)); }
180
181static bool all_mode(mpl_placer **ps, size_t n, mpm_mode m) {
182    for (size_t k = 0; k < n; k++)
183        if (!mpm_mode_eq(mpl_mode(ps[k]), &m)) return false;
184    return true;
185}
186
187static mpl_acts request(mpl_placer *p, const char *words, uint64_t handle, uint64_t t) {
188    mt_strs x = w(words);
189    mpl_acts a = mpl_request(p, &x, handle, t);
190    mt_strs_free(&x);
191    return a;
192}
193
194static void change_protocol(void) {
195    uint64_t t = 1000;
196    mpl_placer **ps = cluster(3, t);
197    logv log = {0};
198    mpl_acts a = request(ps[0], "pin 2", 41, t);
199    run(ps, 3, 0, &a, t, &log);
200    for (int k = 0; k < 3; k++) CHECK(mpm_mode_eq(mpl_mode(ps[k]), &(mpm_mode){.kind = MPM_PIN, .pin = 2}) && !mpl_paused(ps[k]) && mpl_members(ps[k]) == 7);
201    mpw_bytes st2 = read_state(3), f0 = {0}, f1 = {0};
202    CHECK(states_written(&log, 0, &f0) == 1 && bytes_eq(f0, st2) && states_written(&log, 1, &f1) == 1 && bytes_eq(f1, st2) && states_written(&log, 2, NULL) == 0);
203    CHECK(answers(&log) == 1 && answer_in(&log, 0)->handle == 41 && answer_in(&log, 0)->ok && mt_starts(answer_in(&log, 0)->text, "pin 2"));
204    CHECK(mpl_role(ps[0]) == MPM_ROLE_MIRROR && mpl_role(ps[1]) == MPM_ROLE_MIRROR && mpl_role(ps[2]) == MPM_ROLE_RUNNER);
205    log_free(&log);
206    /* pin 2 -> split 1,0 (system 1 owns, 2 mirrors): the runner's state goes to everyone else. */
207    a = request(ps[0], "split 1,0", 42, t);
208    run(ps, 3, 0, &a, t, &log);
209    CHECK(all_mode(ps, 3, split4(1, 1, 0, 0)));
210    CHECK(states_written(&log, 0, &f0) == 1 && bytes_eq(f0, st2) && states_written(&log, 1, &f1) == 1 && bytes_eq(f1, st2));
211    CHECK(mt_contains(answer_in(&log, 0)->text, "state from system 2"));
212    CHECK(mpl_role(ps[0]) == MPM_ROLE_FOLLOWER && mpl_role(ps[1]) == MPM_ROLE_OWNER && mpl_role(ps[2]) == MPM_ROLE_MIRROR);
213    log_free(&log);
214    mpw_bytes_free(&st2);
215    /* Already there; refused capability; a split that does not fit; not the leader; a second change while one runs. */
216    a = request(ps[0], "split 1,0", 1, t);
217    CHECK(answer_of(&a)->ok && !strcmp(answer_of(&a)->text, "split 1,1,0,0 already"));
218    mpl_acts_free(&a);
219    mps_spec pin_spec = spec();
220    pin_spec.cap = MESH_PLACE_CAP_PIN;
221    mpl_placer *pin_only = mpl_new("p", pin_spec, 0, 3);
222    a = mpl_view(pin_only, 7, true, 0, NULL, t);
223    mpl_acts_free(&a);
224    a = request(pin_only, "split 0,1,1,1", 2, t);
225    CHECK(!answer_of(&a)->ok);
226    mpl_acts_free(&a);
227    mpl_free(pin_only);
228    a = request(ps[0], "pin 3", 3, t);
229    CHECK(!answer_of(&a)->ok);
230    mpl_acts_free(&a);
231    a = request(ps[2], "local", 4, t);
232    CHECK(!answer_of(&a)->ok && mt_contains(answer_of(&a)->text, "leader, system 0"));
233    mpl_acts_free(&a);
234    mpl_acts first = request(ps[0], "local", 5, t);
235    size_t sends = 0;
236    for (size_t i = 0; i < first.n; i++)
237        if (first.v[i].kind == MPL_SEND) {
238            sends++;
239            CHECK(first.v[i].dest.to_mask && first.v[i].dest.k == 6);
240        }
241    CHECK(mpl_paused(ps[0]) && sends == 1);
242    mpl_acts_free(&first);
243    a = request(ps[0], "pin 1", 6, t);
244    CHECK(!answer_of(&a)->ok && mt_contains(answer_of(&a)->text, "in progress"));
245    mpl_acts_free(&a);
246    free_cluster(ps, 3);
247}
248
249static void split_auto(void) {
250    uint64_t t = 1000;
251    mpl_placer **ps = cluster(3, t);
252    uint32_t ns[] = {100, 100, 150};
253    for (int k = 0; k < 3; k++) mpl_set_part_ns(ps[k], ns[k]);
254    logv log = {0};
255    mpl_acts a = request(ps[0], "split auto", 9, t);
256    run(ps, 3, 0, &a, t, &log);
257    CHECK(all_mode(ps, 3, split4(0, 0, 1, 2)));
258    CHECK(mt_starts(answer_in(&log, 0)->text, "split 0,0,1,2"));
259    log_free(&log);
260    free_cluster(ps, 3);
261}
262
263static const mpl_act *first_send(const mpl_acts *a) {
264    for (size_t i = 0; i < a->n; i++)
265        if (a->v[i].kind == MPL_SEND) return &a->v[i];
266    return NULL;
267}
268
269static void times_out(void) {
270    uint64_t t = 1000;
271    mpl_placer **ps = cluster(3, t);
272    mpl_acts a = request(ps[0], "pin 1", 7, t);
273    mpw_place pause = place_msg(&first_send(&a)->bytes);
274    CHECK(pause.op == MESH_PL_PAUSE);
275    mpw_place pause2 = place_msg(&first_send(&a)->bytes);
276    mpl_acts x = mpl_peer_place(ps[1], pause, 0, t);
277    mpl_acts_free(&x);
278    x = mpl_peer_place(ps[2], pause2, 0, t);
279    mpl_acts_free(&x);
280    statectx s2 = {2}, s1 = {1};
281    mpl_acts paused = mpl_tick(ps[1], t, true, state_cb, &s2);
282    x = mpl_peer_place(ps[0], place_msg(&first_send(&paused)->bytes), 1, t);
283    mpl_acts_free(&x);
284    mpl_acts_free(&paused);
285    x = mpl_tick(ps[2], t, false, state_cb, &s2);
286    CHECK(x.n == 0 && mpl_paused(ps[2]));
287    mpl_acts_free(&x);
288    x = mpl_tick(ps[0], t, true, state_cb, &s1);
289    CHECK(x.n == 0);
290    mpl_acts_free(&x);
291    mpl_acts acts = mpl_tick(ps[0], t + MPL_CHANGE_TIMEOUT_US + 1000, true, state_cb, &s1);
292    CHECK(!answer_of(&acts)->ok && mt_contains(answer_of(&acts)->text, "system(s) 2 did not drain"));
293    CHECK(!mpl_paused(ps[0]) && mpl_mode(ps[0])->kind == MPM_LOCAL);
294    const mpl_act *fail = first_send(&acts);
295    mpw_place fm = place_msg(&fail->bytes);
296    CHECK(fail->dest.to_mask && fail->dest.k == 6 && fm.op == MESH_PL_FAIL && fm.mode.kind == MPM_LOCAL);
297    mpw_place_free(&fm);
298    for (int k = 1; k < 3; k++) {
299        x = mpl_peer_place(ps[k], place_msg(&fail->bytes), 0, t);
300        mpl_acts_free(&x);
301        CHECK(!mpl_paused(ps[k]) && mpl_mode(ps[k])->kind == MPM_LOCAL);
302    }
303    mpl_acts_free(&acts);
304    mpl_acts_free(&a);
305    free_cluster(ps, 3);
306}
307
308/* One frame of a split over n machines (fanout 4), driven by hand: every machine must show the same
309 * whole frame. */
310static void split_frame(uint32_t n, mpm_mode mode) {
311    uint64_t t = 1000;
312    mps_spec s = spec();
313    mpl_placer **ps = cluster(n, t);
314    char *label = mpm_label(&mode);
315    logv log = {0};
316    mpl_acts a = request(ps[0], label, 1, t);
317    run(ps, n, 0, &a, t, &log);
318    log_free(&log);
319    free(label);
320    CHECK(all_mode(ps, n, mode));
321    uint32_t owner = mpm_owner(&mode.a);
322    mpw_bytes st = read_state((uint8_t)(owner + 1));
323    bool shown[8] = {0};
324    uint32_t shown_sys[8];
325    uint8_t *shown_buf[8] = {0};
326    /* wire: (from, act) */
327    logv wire = {0};
328    mpw_bytes stc = {0};
329    mpw_put(&stc, st.b, st.n);
330    mpl_acts own = mpl_own_frame(ps[owner], 10, stc, gather(mpm_mask_of(&mode.a, owner), (uint8_t)(owner + 1)), t);
331    for (size_t i = 0; i < own.n; i++) {
332        if (own.v[i].kind == MPL_SHOW) {
333            CHECK(!shown[owner]);
334            shown[owner] = true, shown_sys[owner] = own.v[i].sys, shown_buf[owner] = own.v[i].bytes.b, own.v[i].bytes.b = NULL;
335        }
336    }
337    take(&wire, owner, &own);
338    while (wire.n) {
339        logged item = wire.v[--wire.n];
340        if (item.a.kind != MPL_SEND) {
341            mpw_bytes_free(&item.a.bytes);
342            free(item.a.text);
343            continue;
344        }
345        for (uint32_t k = 0; k < n; k++) {
346            bool to = item.a.dest.to_mask ? (k != item.k && (item.a.dest.k >> k & 1)) : k == item.a.dest.k;
347            if (!to) continue;
348            mpl_acts r = mpl_peer_frame(ps[k], frame_msg(&item.a.bytes), t);
349            bool follow = false;
350            for (size_t i = 0; i < r.n; i++) follow |= r.v[i].kind == MPL_FOLLOW && r.v[i].frame == 10;
351            if (follow) {
352                bool wrote = false;
353                for (size_t i = 0; i < r.n; i++) wrote |= r.v[i].kind == MPL_WRITE_STATE && bytes_eq(r.v[i].bytes, st);
354                CHECK(wrote);
355            }
356            for (size_t i = 0; i < r.n; i++)
357                if (r.v[i].kind == MPL_SHOW) {
358                    CHECK(!shown[k] && r.v[i].frame == 10);
359                    shown[k] = true, shown_sys[k] = r.v[i].sys, shown_buf[k] = r.v[i].bytes.b, r.v[i].bytes.b = NULL;
360                }
361            take(&wire, k, &r);
362            if (follow) {
363                mpl_acts done = mpl_follow_done(ps[k], gather(mpm_mask_of(&mode.a, k), (uint8_t)(k + 1)), t);
364                for (size_t i = 0; i < done.n; i++)
365                    if (done.v[i].kind == MPL_SHOW) {
366                        CHECK(!shown[k]);
367                        shown[k] = true, shown_sys[k] = done.v[i].sys, shown_buf[k] = done.v[i].bytes.b, done.v[i].bytes.b = NULL;
368                    }
369                take(&wire, k, &done);
370            }
371        }
372        mpw_bytes_free(&item.a.bytes);
373    }
374    free(wire.v);
375    /* The whole frame: part b from system of[b]'s render, the same on every machine. */
376    uint8_t want[0x80] = {0};
377    char *err = NULL;
378    uint32_t nib = 0;
379    for (uint32_t b = 0; b < 4; b++) {
380        uint32_t k = mode.a.of[b];
381        mpw_bytes g = gather(1u << b, (uint8_t)(k + 1));
382        CHECK(mpc_scatter(&s, want, sizeof want, 1u << b, g.b, g.n, &err));
383        mpw_bytes_free(&g);
384        nib |= k << (4 * b);
385    }
386    for (uint32_t k = 0; k < n; k++) {
387        if (!CHECK(shown[k])) {
388            fprintf(stderr, "system %u showed nothing\n", k);
389            continue;
390        }
391        CHECK(shown_sys[k] == nib && !memcmp(shown_buf[k], want, sizeof want));
392        free(shown_buf[k]);
393    }
394    for (uint32_t k = 0; k < n; k++) CHECK_U(mpl_stats_of(ps[k])->ignored, 0);
395    mpw_bytes_free(&st);
396    mps_free(&s);
397    free_cluster(ps, n);
398}
399
400static void follower_newest(void) {
401    uint64_t t = 1000;
402    mpl_placer **ps = cluster(3, t);
403    logv log = {0};
404    mpl_acts a = request(ps[0], "split 0,0,1,2", 1, t);
405    run(ps, 3, 0, &a, t, &log);
406    log_free(&log);
407    mpw_bytes st = read_state(1);
408    mpw_frame frames[3];
409    for (uint32_t f = 10; f < 13; f++) {
410        mpw_bytes stc = {0};
411        mpw_put(&stc, st.b, st.n);
412        mpl_acts own = mpl_own_frame(ps[0], f, stc, gather(3, 1), t);
413        frames[f - 10] = frame_msg(&first_send(&own)->bytes);
414        mpl_acts_free(&own);
415    }
416    mpl_acts r = mpl_peer_frame(ps[1], frames[0], t);
417    CHECK(r.n == 2 && r.v[0].kind == MPL_WRITE_STATE && r.v[1].kind == MPL_FOLLOW && r.v[1].frame == 10);
418    mpl_acts_free(&r);
419    for (int i = 1; i < 3; i++) {
420        r = mpl_peer_frame(ps[1], frames[i], t);
421        CHECK(r.n == 0);
422        mpl_acts_free(&r);
423    }
424    CHECK_U(mpl_stats_of(ps[1])->ignored, 1);
425    mpl_acts done = mpl_follow_done(ps[1], gather(4, 2), t);
426    CHECK(done.n && done.v[done.n - 1].kind == MPL_FOLLOW && done.v[done.n - 1].frame == 12);
427    mpl_acts_free(&done);
428    /* A frame from a system with other parts, or state from a non-owner, is ignored. */
429    mpw_bytes g4 = gather(4, 3), g8 = gather(8, 3);
430    mpw_frame stray = {.name = "doom", .frame = 3, .parts = 4, .sys = 2, .data = g4.b, .ndata = g4.n};
431    r = mpl_peer_frame(ps[0], mpw_frame_clone(&stray), t);
432    CHECK(r.n == 0);
433    mpl_acts_free(&r);
434    mpw_frame forged = {.name = "doom", .frame = 3, .parts = 8, .sys = 2, .state = st.b, .nstate = st.n, .data = g8.b, .ndata = g8.n};
435    r = mpl_peer_frame(ps[1], mpw_frame_clone(&forged), t);
436    CHECK(r.n == 0);
437    mpl_acts_free(&r);
438    CHECK(mpl_stats_of(ps[0])->ignored == 1 && mpl_stats_of(ps[1])->ignored == 2);
439    mpw_bytes_free(&g4), mpw_bytes_free(&g8), mpw_bytes_free(&st);
440    free_cluster(ps, 3);
441}
442
443static bool logged_line(const logv *l, const char *needle) {
444    for (size_t i = 0; i < l->n; i++)
445        if (l->v[i].a.kind == MPL_LOG && mt_contains(l->v[i].a.text, needle)) return true;
446    return false;
447}
448
449static void member_loss(void) {
450    uint64_t t = 1000;
451    mpl_placer **ps = cluster(3, t);
452    logv log = {0};
453    mpl_acts a = request(ps[0], "split", 1, t);
454    run(ps, 3, 0, &a, t, &log);
455    log_free(&log);
456    CHECK(all_mode(ps, 3, split4(0, 0, 1, 2)));
457    /* System 2 dies: the leader re-plans `split` over 0 and 1 at once. */
458    mt_strs sw = w("split");
459    logv out = {0};
460    for (uint32_t k = 0; k < 2; k++) {
461        mpl_acts v = mpl_view(ps[k], 3, true, 0, &sw, t);
462        take(&out, k, &v);
463    }
464    mpl_placer *live[3] = {ps[0], ps[1], NULL};
465    pump(live, 2, &out, t, &log);
466    CHECK(all_mode(live, 2, split4(0, 0, 1, 1)) && mpl_members(live[0]) == 3 && mpl_members(live[1]) == 3);
467    CHECK(logged_line(&log, "re-planning"));
468    log_free(&log);
469    mt_strs_free(&sw);
470    free_cluster(ps, 3);
471    /* Pinned to the member that dies: the leader runs it, from its own (mirror) copy of the state. */
472    ps = cluster(3, t);
473    a = request(ps[0], "pin 2", 2, t);
474    run(ps, 3, 0, &a, t, &log);
475    log_free(&log);
476    mt_strs pw = w("pin 2");
477    for (uint32_t k = 0; k < 2; k++) {
478        mpl_acts v = mpl_view(ps[k], 3, true, 0, &pw, t);
479        take(&out, k, &v);
480    }
481    live[0] = ps[0], live[1] = ps[1];
482    pump(live, 2, &out, t, &log);
483    CHECK(all_mode(live, 2, mpm_pin(0)));
484    CHECK(logged_line(&log, "system 2's state is gone"));
485    mpw_bytes s1 = read_state(1), got = {0};
486    CHECK(states_written(&log, 1, &got) == 1 && bytes_eq(got, s1));
487    log_free(&log);
488    mpw_bytes_free(&s1);
489    /* System 2 comes back: after the dwell, the leader moves it back to its pin (the request). */
490    mpl_free(ps[2]);
491    ps[2] = mpl_new("doom", spec(), 2, 3);
492    a = mpl_view(ps[2], 7, true, 0, &pw, t);
493    mpl_acts_free(&a);
494    for (int k = 0; k < 2; k++) {
495        a = mpl_view(ps[k], 7, true, 0, &pw, t);
496        CHECK(a.n == 0);
497        mpl_acts_free(&a);
498    }
499    statectx c1 = {1};
500    a = mpl_tick(ps[0], t, true, state_cb, &c1);
501    CHECK(a.n == 0);
502    mpl_acts_free(&a);
503    a = mpl_tick(ps[0], t + MPL_JOIN_DWELL_US, true, state_cb, &c1);
504    run(ps, 3, 0, &a, t + MPL_JOIN_DWELL_US, &log);
505    log_free(&log);
506    CHECK(all_mode(ps, 3, mpm_pin(2)) && mpl_members(ps[0]) == 7 && mpl_members(ps[2]) == 7);
507    /* No leader, and the runner is gone: local here. */
508    a = mpl_view(ps[1], 3, false, 0, &pw, t);
509    CHECK(a.n && a.v[a.n - 1].kind == MPL_MODE && a.v[a.n - 1].mode.kind == MPM_LOCAL && mpl_mode(ps[1])->kind == MPM_LOCAL);
510    mpl_acts_free(&a);
511    a = mpl_view(ps[1], 3, false, 0, &pw, t);
512    CHECK(a.n == 0);
513    mpl_acts_free(&a);
514    mt_strs_free(&pw);
515    free_cluster(ps, 3);
516}
517
518int main(void) {
519    change_protocol();
520    split_auto();
521    times_out();
522    split_frame(2, split4(0, 0, 1, 1));
523    split_frame(3, split4(0, 0, 1, 2));
524    split_frame(3, split4(2, 1, 2, 1));
525    split_frame(4, split4(0, 1, 2, 3));
526    split_frame(4, split4(1, 1, 2, 0));
527    split_frame(3, split4(2, 2, 2, 2));
528    follower_newest();
529    member_loss();
530    return mesh_check_done();
531}