==> tests/table.c <==1/* The job table: jobs.rs's `ops_frames_and_the_merge` (its table half), and random op sequences
2 * applied identically here and in the Rust, compared by the hash of the encoded table. */
3#include "mesh/jobs/table.h"
4#include "mesh/check.h"
5#include "gold_table.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11static uint64_t fnv(const void *p, size_t n) {
12 const uint8_t *b = p;
13 uint64_t h = 0xcbf29ce484222325u;
14 for (size_t i = 0; i < n; i++) {
15 h ^= b[i];
16 h *= 0x100000001b3u;
17 }
18 return h;
19}
20
21static mj_spec mkspec(mj_job_id id, const char *items, int32_t prio, uint32_t members, bool setup) {
22 mj_spec s = {.id = id, .program = mstrdup("p"), .op = 254, .priority = prio, .attempts = 3, .mode = MJ_MODE_COLLECT, .submitted_ms = 5, .members = members};
23 if (!mj_ranges_parse(items, &s.items, NULL)) abort();
24 if (setup) {
25 mj_setup su = {6, 0};
26 s.setup = mj_setup_clone(&su, 1);
27 s.nsetup = 1;
28 }
29 return s;
30}
31
32static mj_res res(uint64_t lease) {
33 mj_res r = {.lease = lease, .member = 2, .result = 9, .out = mxalloc(3), .out_len = 3};
34 memcpy(r.out, (uint8_t[]){1, 2, 3}, 3);
35 if (lease % 2 == 1) {
36 r.has_blob = true;
37 memset(r.blob.hash.b, (int)lease, 32);
38 r.blob.len = 9216;
39 r.blob.holders = 0xb;
40 }
41 return r;
42}
43
44static void the_rust_test(void) {
45 mj_spec s = mkspec((uint64_t)1 << 32 | 1, "0-10", 0, 5, true);
46 mj_res r7 = res(7), r99 = res(99), r1 = res(1), r2 = res(2);
47 mj_ops ops = {0};
48 mj_ops_push(&ops, mj_op_submit(&s));
49 mj_ops_push(&ops, mj_op_done(s.id, 3, &r7));
50 mj_ops_push(&ops, mj_op_done_run(s.id, 4, 6, 8, 1));
51 mj_ops_push(&ops, mj_op_fail(s.id, 9, 3, "boom"));
52 mj_ops_push(&ops, mj_op_fails(s.id, 2, 1));
53 mj_ops_push(&ops, mj_op_dupes(s.id, 4));
54 mj_ops_push(&ops, mj_op_finish(s.id, 77, MESH_JOBS_FIN_DONE));
55 mj_ops_push(&ops, mj_op_cancel(s.id, "x"));
56 mj_ops_push(&ops, mj_op_tone(s.id, 2, 1));
57 msb enc = {0};
58 mj_encode_ops(ops.v, ops.n, &enc);
59 mj_ops back = {0};
60 CHECK(mj_decode_ops(enc.p, enc.n, &back, NULL) && mj_ops_eq(&back, &ops));
61 mj_ops_free(&back);
62 mj_table a;
63 mj_table_init(&a);
64 for (size_t i = 0; i < ops.n; i++) mj_table_apply(&a, &ops.v[i]);
65 /* Idempotent; a second result for item 3 (another lease) does not replace the first. */
66 mj_table b = mj_table_clone(&a);
67 for (size_t i = 0; i < ops.n; i++) mj_table_apply(&b, &ops.v[i]);
68 mj_op late = mj_op_done(s.id, 3, &r99);
69 mj_table_apply(&b, &late);
70 CHECK(mj_table_eq(&a, &b));
71 /* The union: two tables with different items merge to both, in either order. */
72 mj_table x, y;
73 mj_table_init(&x);
74 mj_table_init(&y);
75 mj_table_apply(&x, &ops.v[0]);
76 mj_table_apply(&y, &ops.v[0]);
77 mj_op d1 = mj_op_done(s.id, 1, &r1), d2 = mj_op_done(s.id, 2, &r2);
78 mj_table_apply(&x, &d1);
79 mj_table_apply(&y, &d2);
80 mj_table xy = mj_table_clone(&x), yx = mj_table_clone(&y);
81 mj_ops xo = {0}, yo = {0};
82 mj_table_ops(&y, &yo);
83 mj_table_ops(&x, &xo);
84 for (size_t i = 0; i < yo.n; i++) mj_table_apply(&xy, &yo.v[i]);
85 for (size_t i = 0; i < xo.n; i++) mj_table_apply(&yx, &xo.v[i]);
86 CHECK(mj_table_eq(&xy, &yx));
87 char *w = mj_ranges_word_new(&mj_table_get(&xy, s.id)->done);
88 CHECK_S(w, "1-3");
89 free(w);
90 /* The table's own ops rebuild it. */
91 mj_ops ao = {0};
92 mj_table_ops(&a, &ao);
93 mj_table c;
94 mj_table_init(&c);
95 for (size_t i = 0; i < ao.n; i++) mj_table_apply(&c, &ao.v[i]);
96 CHECK(mj_table_eq(&c, &a));
97 uint64_t lease;
98 CHECK(mj_job_lease_of(mj_table_get(&a, s.id), 5, &lease) && lease == 8);
99 CHECK(mj_job_lease_of(mj_table_get(&a, s.id), 3, &lease) && lease == 7);
100 CHECK(!mj_job_lease_of(mj_table_get(&a, s.id), 0, &lease));
101 CHECK_S(mj_job_state(mj_table_get(&a, s.id)), "done-with-failures");
102 for (mj_table *t[] = {&a, &b, &x, &y, &xy, &yx, &c}, **p = t; p < t + 7; p++) mj_table_free(*p);
103 mj_ops_free(&xo);
104 mj_ops_free(&yo);
105 mj_ops_free(&ao);
106 mj_ops_free(&ops);
107 mj_op_free(&late);
108 mj_op_free(&d1);
109 mj_op_free(&d2);
110 mj_res_free(&r7);
111 mj_res_free(&r99);
112 mj_res_free(&r1);
113 mj_res_free(&r2);
114 mj_spec_free(&s);
115 msb_free(&enc);
116}
117
118/* ---- the generator the Rust golden test runs (tests/gold_table.rs) ------------------------- */
119
120static uint64_t rs;
121static uint64_t rnd(uint64_t n) {
122 rs ^= rs << 13;
123 rs ^= rs >> 7;
124 rs ^= rs << 17;
125 return rs % (n ? n : 1);
126}
127
128static mj_spec gspec(uint64_t k) {
129 if (k == 0) return mkspec((uint64_t)1 << 32 | 1, "0-200", 0, 0, false);
130 if (k == 1) return mkspec((uint64_t)1 << 32 | 2, "0-", 1, 0, false);
131 return mkspec((uint64_t)2 << 32 | 1, "5-50,100-", 2, 0, false);
132}
133
134static mj_op gen(void) {
135 uint64_t k = rnd(3);
136 mj_spec sp = gspec(k);
137 mj_job_id job = sp.id;
138 mj_op o;
139 switch (rnd(10)) {
140 case 0: o = mj_op_submit(&sp); break;
141 case 1:
142 case 2:
143 case 3: {
144 uint64_t item = rnd(220);
145 mj_res r = {0};
146 r.lease = rnd(5);
147 r.member = (uint32_t)rnd(4);
148 r.result = (uint32_t)rnd(1000);
149 r.out_len = (size_t)rnd(3);
150 r.out = r.out_len ? mxalloc(r.out_len) : NULL;
151 for (size_t i = 0; i < r.out_len; i++) r.out[i] = (uint8_t)rnd(256);
152 if (rnd(2) == 1) {
153 uint8_t f = (uint8_t)rnd(256);
154 r.has_blob = true;
155 memset(r.blob.hash.b, f, 32);
156 r.blob.len = (uint32_t)rnd(10000);
157 r.blob.holders = (uint32_t)rnd(16);
158 }
159 o = mj_op_done(job, item, &r);
160 mj_res_free(&r);
161 break;
162 }
163 case 4:
164 case 5: {
165 uint64_t s = rnd(220), e = s + rnd(30), lease = rnd(5);
166 uint32_t member = (uint32_t)rnd(4);
167 o = mj_op_done_run(job, s, e, lease, member);
168 break;
169 }
170 case 6: {
171 uint64_t item = rnd(220);
172 uint32_t tries = (uint32_t)rnd(4);
173 char why[8];
174 snprintf(why, sizeof why, "w%llu", (unsigned long long)rnd(9));
175 o = mj_op_fail(job, item, tries, why);
176 break;
177 }
178 case 7: {
179 uint64_t item = rnd(220);
180 o = mj_op_fails(job, item, (uint32_t)rnd(5));
181 break;
182 }
183 case 8:
184 switch (rnd(4)) {
185 case 0: o = mj_op_dupes(job, rnd(10)); break;
186 case 1: {
187 uint64_t at = rnd(100);
188 o = mj_op_finish(job, at, 1 + (uint32_t)rnd(3));
189 break;
190 }
191 case 2: {
192 char why[8];
193 snprintf(why, sizeof why, "c%llu", (unsigned long long)rnd(9));
194 o = mj_op_cancel(job, why);
195 break;
196 }
197 default: {
198 uint32_t seq = (uint32_t)rnd(4);
199 o = mj_op_tone(job, seq, (uint32_t)rnd(2));
200 }
201 }
202 break;
203 default: {
204 if (rnd(20) == 0) {
205 o = mj_op_forget(job);
206 break;
207 }
208 uint64_t upto = rnd(220), n = rnd(3);
209 mj_tally tl[3];
210 for (uint64_t i = 0; i < n; i++) {
211 tl[i].member = (uint32_t)rnd(4);
212 tl[i].n = rnd(50);
213 }
214 o = mj_op_fold(job, upto, tl, (size_t)n);
215 }
216 }
217 mj_spec_free(&sp);
218 return o;
219}
220
221static void gold(void) {
222 size_t g = 0, ng = sizeof GOLD / sizeof *GOLD;
223 char line[256];
224 for (uint64_t seeds[] = {1, 7, 42, 1234}, *sd = seeds; sd < seeds + 4; sd++) {
225 rs = *sd * 0x9e3779b97f4a7c15u | 1;
226 mj_table t;
227 mj_table_init(&t);
228 mj_ops all = {0};
229 for (int i = 1; i <= 600; i++) {
230 mj_op o = gen();
231 mj_table_apply(&t, &o);
232 mj_ops_push(&all, o);
233 if (i % 50 == 0) {
234 mj_ops to = {0};
235 mj_table_ops(&t, &to);
236 msb enc = {0};
237 mj_encode_ops(to.v, to.n, &enc);
238 mj_table c;
239 mj_table_init(&c);
240 for (size_t k = 0; k < to.n; k++) mj_table_apply(&c, &to.v[k]);
241 mj_ops co = {0};
242 mj_table_ops(&c, &co);
243 msb enc2 = {0};
244 mj_encode_ops(co.v, co.n, &enc2);
245 snprintf(line, sizeof line, "%llu %d %016llx %zu %016llx %d", (unsigned long long)*sd, i, (unsigned long long)fnv(enc.p, enc.n), enc.n,
246 (unsigned long long)fnv(enc2.p, enc2.n), (int)mj_table_eq(&c, &t));
247 if (CHECK(g < ng)) CHECK_S(line, GOLD[g]);
248 g++;
249 mj_table_free(&c);
250 mj_ops_free(&to);
251 mj_ops_free(&co);
252 msb_free(&enc);
253 msb_free(&enc2);
254 }
255 }
256 msb enc = {0};
257 mj_encode_ops(all.v, all.n, &enc);
258 snprintf(line, sizeof line, "%llu ops %016llx %zu", (unsigned long long)*sd, (unsigned long long)fnv(enc.p, enc.n), enc.n);
259 if (CHECK(g < ng)) CHECK_S(line, GOLD[g]);
260 g++;
261 /* Decoding the whole sequence gives it back. */
262 mj_ops back = {0};
263 CHECK(mj_decode_ops(enc.p, enc.n, &back, NULL) && mj_ops_eq(&back, &all));
264 mj_ops_free(&back);
265 snprintf(line, sizeof line, "%llu next %llx %llx", (unsigned long long)*sd, (unsigned long long)mj_table_next_id(&t, 1),
266 (unsigned long long)mj_table_next_id(&t, 2));
267 if (CHECK(g < ng)) CHECK_S(line, GOLD[g]);
268 g++;
269 msb_free(&enc);
270 mj_ops_free(&all);
271 mj_table_free(&t);
272 }
273 CHECK_U(g, ng);
274}
275
276int main(void) {
277 the_rust_test();
278 gold();
279 return mesh_check_done();
280}