==> tests/log.c <==1/* The job log: random record sequences replayed here and in the Rust to the same replica; the Rust
2 * test's log (what is committed and what is not, a torn tail); review finding 6's snapshot; and
3 * replay's answers to corrupt records. */
4#include "mesh/jobs/log.h"
5#include "mesh/check.h"
6#include "gen.h"
7#include "gold_log.h"
8
9static uint64_t fnv(const void *p, size_t n) {
10 const uint8_t *b = p;
11 uint64_t h = 0xcbf29ce484222325u;
12 for (size_t i = 0; i < n; i++) {
13 h ^= b[i];
14 h *= 0x100000001b3u;
15 }
16 return h;
17}
18
19static size_t unhexs(const char *h, uint8_t *out) {
20 size_t n = strlen(h) / 2;
21 for (size_t i = 0; i < n; i++) {
22 unsigned v;
23 sscanf(h + 2 * i, "%2x", &v);
24 out[i] = (uint8_t)v;
25 }
26 return n;
27}
28
29static void encode_logs(const mj_logs *v, msb *out) {
30 for (size_t i = 0; i < v->n; i++) mj_log_encode(&v->v[i], out);
31}
32
33static void random_sequences(void) {
34 size_t g = 0, ng = sizeof GOLD_SEQ / sizeof *GOLD_SEQ;
35 char line[256];
36 for (uint64_t seeds[] = {3, 5, 11, 99}, *sd = seeds; sd < seeds + 4; sd++) {
37 rs = *sd * 0x9e3779b97f4a7c15u | 1;
38 mj_replica rep;
39 mj_replica_init(&rep);
40 msb bytes = {0};
41 for (int i = 1; i <= 400; i++) {
42 uint64_t hi = 1 + rnd(3);
43 uint64_t lid = hi << 40 | rnd(3);
44 mj_log l;
45 switch (rnd(8)) {
46 case 0: l = mj_log_lid(lid); break;
47 case 1: {
48 uint64_t n = rnd(6), k = rnd(4);
49 mj_ops ops = {0};
50 for (uint64_t j = 0; j < k; j++) mj_ops_push(&ops, gen());
51 l = mj_log_snapshot(lid, n, ops);
52 break;
53 }
54 case 2:
55 case 3:
56 case 4: {
57 uint64_t n = rnd(8);
58 bool replace = rnd(4) == 0;
59 uint64_t k = rnd(4);
60 mj_ops ops = {0};
61 for (uint64_t j = 0; j < k; j++) mj_ops_push(&ops, gen());
62 l = mj_log_entry(lid, n, replace, ops);
63 break;
64 }
65 default: l = mj_log_commit(lid, rnd(8));
66 }
67 mj_log_encode(&l, &bytes);
68 mj_replica_apply(&rep, &l);
69 mj_log_free(&l);
70 if (i % 40 == 0) {
71 mj_logs lg = mj_replica_log(&rep);
72 msb c = {0};
73 encode_logs(&lg, &c);
74 snprintf(line, sizeof line, "%llu %d %016llx %zu lid %llx commit %llx,%llu pending %zu last %llu", (unsigned long long)*sd, i,
75 (unsigned long long)fnv(c.p, c.n), c.n, (unsigned long long)rep.lid, (unsigned long long)rep.commit_lid,
76 (unsigned long long)rep.commit_n, rep.npending, (unsigned long long)mj_replica_last(&rep, (uint64_t)1 << 40));
77 if (CHECK(g < ng)) CHECK_S(line, GOLD_SEQ[g]);
78 g++;
79 /* The compacted log replays to the same replica. */
80 mj_replica again;
81 CHECK(mj_replay(c.p, c.n, &again, NULL, NULL, NULL) && mj_replica_eq(&again, &rep));
82 mj_replica_free(&again);
83 mj_logs_free(&lg);
84 msb_free(&c);
85 }
86 }
87 mj_replica rr;
88 size_t n = 0, at = 0;
89 CHECK(mj_replay(bytes.p, bytes.n, &rr, &n, &at, NULL) && mj_replica_eq(&rr, &rep));
90 snprintf(line, sizeof line, "%llu replay %zu %zu %016llx", (unsigned long long)*sd, n, at, (unsigned long long)fnv(bytes.p, bytes.n));
91 if (CHECK(g < ng)) CHECK_S(line, GOLD_SEQ[g]);
92 g++;
93 mj_replica_free(&rr);
94 mj_replica_free(&rep);
95 msb_free(&bytes);
96 }
97 CHECK_U(g, ng);
98}
99
100static mj_res res(uint64_t lease) {
101 mj_res r = {.lease = lease, .member = 2, .result = 9, .out = mxalloc(3), .out_len = 3};
102 memcpy(r.out, (uint8_t[]){1, 2, 3}, 3);
103 return r;
104}
105
106static mj_ops one(mj_op o) {
107 mj_ops v = {0};
108 mj_ops_push(&v, o);
109 return v;
110}
111
112/* The log section of the Rust's ops_frames_and_the_merge. */
113static void the_rust_log(void) {
114 mj_spec s = gspec(0);
115 uint64_t l = (uint64_t)1 << 40;
116 mj_res r1 = res(1), r3 = res(3);
117 mj_table x;
118 mj_table_init(&x);
119 mj_op sub = mj_op_submit(&s), d1 = mj_op_done(s.id, 1, &r1);
120 mj_table_apply(&x, &sub);
121 mj_table_apply(&x, &d1);
122 msb log = {0};
123 mj_log a = mj_log_lid(l);
124 mj_log_encode(&a, &log);
125 mj_ops xo = {0};
126 mj_table_ops(&x, &xo);
127 mj_log snap = mj_log_snapshot(l, 3, xo);
128 mj_log_encode(&snap, &log);
129 mj_log e4 = mj_log_entry(l, 4, false, one(mj_op_done(s.id, 7, &r3)));
130 mj_log e5 = mj_log_entry(l, 5, false, one(mj_op_done(s.id, 6, &r3)));
131 mj_log c4 = mj_log_commit(l, 4);
132 mj_log_encode(&e4, &log);
133 mj_log_encode(&e5, &log);
134 mj_log_encode(&c4, &log);
135 static uint8_t want[8192];
136 size_t wn = unhexs(GOLD_TESTLOG, want);
137 CHECK(log.n == wn && memcmp(log.p, want, wn) == 0);
138 size_t whole = log.n;
139 mj_log e6 = mj_log_entry(l, 6, false, one(mj_op_done(s.id, 8, &r3)));
140 msb e6b = {0};
141 mj_log_encode(&e6, &e6b);
142 msb_put(&log, e6b.p, 20);
143 mj_replica r;
144 size_t n, at;
145 CHECK(mj_replay(log.p, log.n, &r, &n, &at, NULL));
146 CHECK_U(n, 5);
147 CHECK_U(at, whole);
148 CHECK_U(r.lid, l);
149 CHECK(r.commit_lid == l && r.commit_n == 4);
150 char *w = mj_ranges_word_new(&mj_table_get(&r.committed, s.id)->done);
151 CHECK_S(w, "1-2,7-8");
152 free(w);
153 CHECK_U(r.npending, 1); /* item 6's entry is not committed: pending, not in the table */
154 /* The replica's own log replays to it (a compaction). */
155 mj_logs lg = mj_replica_log(&r);
156 msb c = {0};
157 encode_logs(&lg, &c);
158 mj_replica again;
159 CHECK(mj_replay(c.p, c.n, &again, NULL, NULL, NULL) && mj_replica_eq(&again, &r));
160 mj_replica_free(&again);
161 mj_logs_free(&lg);
162 msb_free(&c);
163 mj_replica_free(&r);
164 for (mj_log *p[] = {&a, &snap, &e4, &e5, &c4, &e6}, **q = p; q < p + 6; q++) mj_log_free(*q);
165 msb_free(&e6b);
166 msb_free(&log);
167 mj_op_free(&sub);
168 mj_op_free(&d1);
169 mj_table_free(&x);
170 mj_res_free(&r1);
171 mj_res_free(&r3);
172 mj_spec_free(&s);
173 /* Corrupt records: the Rust's answers. */
174 for (size_t i = 0; i < sizeof GOLD_BAD / sizeof *GOLD_BAD; i++) {
175 uint8_t b[256];
176 size_t bn = unhexs(GOLD_BAD[i][0], b);
177 mj_replica q;
178 char *err = NULL;
179 if (mj_replay(b, bn, &q, &n, &at, &err)) {
180 char got[64];
181 snprintf(got, sizeof got, "ok %zu %zu", n, at);
182 CHECK_S(got, GOLD_BAD[i][1]);
183 mj_replica_free(&q);
184 } else {
185 CHECK_S(err, GOLD_BAD[i][1]);
186 free(err);
187 }
188 }
189}
190
191/* Review finding 6 (the Rust's a_snapshot_keeps_the_entries_acknowledged_past_it, replica half). */
192static void snapshot_keeps_entries(void) {
193 uint64_t l = (uint64_t)3 << 40;
194 mj_replica r;
195 mj_replica_init(&r);
196 mj_log seq[4] = {mj_log_lid(l), mj_log_snapshot(l, 8, (mj_ops){0}), mj_log_entry(l, 9, false, one(mj_op_fails(1, 9, 1))),
197 mj_log_entry(l, 10, false, one(mj_op_fails(1, 10, 1)))};
198 for (int i = 0; i < 4; i++) {
199 mj_replica_apply(&r, &seq[i]);
200 mj_log_free(&seq[i]);
201 }
202 mj_spec s = gspec(0);
203 mj_log s8 = mj_log_snapshot(l, 8, one(mj_op_submit(&s)));
204 mj_replica_apply(&r, &s8);
205 CHECK(mj_replica_last(&r, l) == 10 && r.npending == 2); /* entries 9 and 10 stay */
206 mj_log s9 = mj_log_snapshot(l, 9, one(mj_op_submit(&s)));
207 mj_replica_apply(&r, &s9);
208 CHECK(mj_replica_last(&r, l) == 10 && r.npending == 1);
209 mj_log_free(&s8);
210 mj_log_free(&s9);
211 mj_spec_free(&s);
212 mj_replica_free(&r);
213}
214
215int main(void) {
216 random_sequences();
217 the_rust_log();
218 snapshot_keeps_entries();
219 return mesh_check_done();
220}