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