==> 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}