1 /*
2  * Runner for littlefs tests
3  *
4  * Copyright (c) 2022, The littlefs authors.
5  * SPDX-License-Identifier: BSD-3-Clause
6  */
7 #ifndef _POSIX_C_SOURCE
8 #define _POSIX_C_SOURCE 199309L
9 #endif
10 
11 #include "runners/test_runner.h"
12 #include "bd/lfs_emubd.h"
13 
14 #include <getopt.h>
15 #include <sys/types.h>
16 #include <errno.h>
17 #include <setjmp.h>
18 #include <fcntl.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <time.h>
23 #include <execinfo.h>
24 
25 
26 // some helpers
27 
28 // append to an array with amortized doubling
mappend(void ** p,size_t size,size_t * count,size_t * capacity)29 void *mappend(void **p,
30         size_t size,
31         size_t *count,
32         size_t *capacity) {
33     uint8_t *p_ = *p;
34     size_t count_ = *count;
35     size_t capacity_ = *capacity;
36 
37     count_ += 1;
38     if (count_ > capacity_) {
39         capacity_ = (2*capacity_ < 4) ? 4 : 2*capacity_;
40 
41         p_ = realloc(p_, capacity_*size);
42         if (!p_) {
43             return NULL;
44         }
45     }
46 
47     *p = p_;
48     *count = count_;
49     *capacity = capacity_;
50     return &p_[(count_-1)*size];
51 }
52 
53 // a quick self-terminating text-safe varint scheme
leb16_print(uintmax_t x)54 static void leb16_print(uintmax_t x) {
55     // allow 'w' to indicate negative numbers
56     if ((intmax_t)x < 0) {
57         printf("w");
58         x = -x;
59     }
60 
61     while (true) {
62         char nibble = (x & 0xf) | (x > 0xf ? 0x10 : 0);
63         printf("%c", (nibble < 10) ? '0'+nibble : 'a'+nibble-10);
64         if (x <= 0xf) {
65             break;
66         }
67         x >>= 4;
68     }
69 }
70 
leb16_parse(const char * s,char ** tail)71 static uintmax_t leb16_parse(const char *s, char **tail) {
72     bool neg = false;
73     uintmax_t x = 0;
74     if (tail) {
75         *tail = (char*)s;
76     }
77 
78     if (s[0] == 'w') {
79         neg = true;
80         s = s+1;
81     }
82 
83     size_t i = 0;
84     while (true) {
85         uintmax_t nibble = s[i];
86         if (nibble >= '0' && nibble <= '9') {
87             nibble = nibble - '0';
88         } else if (nibble >= 'a' && nibble <= 'v') {
89             nibble = nibble - 'a' + 10;
90         } else {
91             // invalid?
92             return 0;
93         }
94 
95         x |= (nibble & 0xf) << (4*i);
96         i += 1;
97         if (!(nibble & 0x10)) {
98             s = s + i;
99             break;
100         }
101     }
102 
103     if (tail) {
104         *tail = (char*)s;
105     }
106     return neg ? -x : x;
107 }
108 
109 
110 
111 // test_runner types
112 
113 typedef struct test_geometry {
114     const char *name;
115     test_define_t defines[TEST_GEOMETRY_DEFINE_COUNT];
116 } test_geometry_t;
117 
118 typedef struct test_powerloss {
119     const char *name;
120     void (*run)(
121             const lfs_emubd_powercycles_t *cycles,
122             size_t cycle_count,
123             const struct test_suite *suite,
124             const struct test_case *case_);
125     const lfs_emubd_powercycles_t *cycles;
126     size_t cycle_count;
127 } test_powerloss_t;
128 
129 typedef struct test_id {
130     const char *name;
131     const test_define_t *defines;
132     size_t define_count;
133     const lfs_emubd_powercycles_t *cycles;
134     size_t cycle_count;
135 } test_id_t;
136 
137 
138 // test suites are linked into a custom ld section
139 extern struct test_suite __start__test_suites;
140 extern struct test_suite __stop__test_suites;
141 
142 const struct test_suite *test_suites = &__start__test_suites;
143 #define TEST_SUITE_COUNT \
144     ((size_t)(&__stop__test_suites - &__start__test_suites))
145 
146 
147 // test define management
148 typedef struct test_define_map {
149     const test_define_t *defines;
150     size_t count;
151 } test_define_map_t;
152 
153 typedef struct test_define_names {
154     const char *const *names;
155     size_t count;
156 } test_define_names_t;
157 
test_define_lit(void * data)158 intmax_t test_define_lit(void *data) {
159     return (intptr_t)data;
160 }
161 
162 #define TEST_CONST(x) {test_define_lit, (void*)(uintptr_t)(x)}
163 #define TEST_LIT(x) ((test_define_t)TEST_CONST(x))
164 
165 
166 #define TEST_DEF(k, v) \
167     intmax_t test_define_##k(void *data) { \
168         (void)data; \
169         return v; \
170     }
171 
172     TEST_IMPLICIT_DEFINES
173 #undef TEST_DEF
174 
175 #define TEST_DEFINE_MAP_OVERRIDE    0
176 #define TEST_DEFINE_MAP_EXPLICIT    1
177 #define TEST_DEFINE_MAP_PERMUTATION 2
178 #define TEST_DEFINE_MAP_GEOMETRY    3
179 #define TEST_DEFINE_MAP_IMPLICIT    4
180 #define TEST_DEFINE_MAP_COUNT       5
181 
182 test_define_map_t test_define_maps[TEST_DEFINE_MAP_COUNT] = {
183     [TEST_DEFINE_MAP_IMPLICIT] = {
184         (const test_define_t[TEST_IMPLICIT_DEFINE_COUNT]) {
185             #define TEST_DEF(k, v) \
186                 [k##_i] = {test_define_##k, NULL},
187 
188                 TEST_IMPLICIT_DEFINES
189             #undef TEST_DEF
190         },
191         TEST_IMPLICIT_DEFINE_COUNT,
192     },
193 };
194 
195 #define TEST_DEFINE_NAMES_SUITE    0
196 #define TEST_DEFINE_NAMES_IMPLICIT 1
197 #define TEST_DEFINE_NAMES_COUNT    2
198 
199 test_define_names_t test_define_names[TEST_DEFINE_NAMES_COUNT] = {
200     [TEST_DEFINE_NAMES_IMPLICIT] = {
201         (const char *const[TEST_IMPLICIT_DEFINE_COUNT]){
202             #define TEST_DEF(k, v) \
203                 [k##_i] = #k,
204 
205                 TEST_IMPLICIT_DEFINES
206             #undef TEST_DEF
207         },
208         TEST_IMPLICIT_DEFINE_COUNT,
209     },
210 };
211 
212 intmax_t *test_define_cache;
213 size_t test_define_cache_count;
214 unsigned *test_define_cache_mask;
215 
test_define_name(size_t define)216 const char *test_define_name(size_t define) {
217     // lookup in our test names
218     for (size_t i = 0; i < TEST_DEFINE_NAMES_COUNT; i++) {
219         if (define < test_define_names[i].count
220                 && test_define_names[i].names
221                 && test_define_names[i].names[define]) {
222             return test_define_names[i].names[define];
223         }
224     }
225 
226     return NULL;
227 }
228 
test_define_ispermutation(size_t define)229 bool test_define_ispermutation(size_t define) {
230     // is this define specific to the permutation?
231     for (size_t i = 0; i < TEST_DEFINE_MAP_IMPLICIT; i++) {
232         if (define < test_define_maps[i].count
233                 && test_define_maps[i].defines[define].cb) {
234             return true;
235         }
236     }
237 
238     return false;
239 }
240 
test_define(size_t define)241 intmax_t test_define(size_t define) {
242     // is the define in our cache?
243     if (define < test_define_cache_count
244             && (test_define_cache_mask[define/(8*sizeof(unsigned))]
245                 & (1 << (define%(8*sizeof(unsigned)))))) {
246         return test_define_cache[define];
247     }
248 
249     // lookup in our test defines
250     for (size_t i = 0; i < TEST_DEFINE_MAP_COUNT; i++) {
251         if (define < test_define_maps[i].count
252                 && test_define_maps[i].defines[define].cb) {
253             intmax_t v = test_define_maps[i].defines[define].cb(
254                     test_define_maps[i].defines[define].data);
255 
256             // insert into cache!
257             test_define_cache[define] = v;
258             test_define_cache_mask[define / (8*sizeof(unsigned))]
259                     |= 1 << (define%(8*sizeof(unsigned)));
260 
261             return v;
262         }
263     }
264 
265     return 0;
266 
267     // not found?
268     const char *name = test_define_name(define);
269     fprintf(stderr, "error: undefined define %s (%zd)\n",
270             name ? name : "(unknown)",
271             define);
272     assert(false);
273     exit(-1);
274 }
275 
test_define_flush(void)276 void test_define_flush(void) {
277     // clear cache between permutations
278     memset(test_define_cache_mask, 0,
279             sizeof(unsigned)*(
280                 (test_define_cache_count+(8*sizeof(unsigned))-1)
281                 / (8*sizeof(unsigned))));
282 }
283 
284 // geometry updates
285 const test_geometry_t *test_geometry = NULL;
286 
test_define_geometry(const test_geometry_t * geometry)287 void test_define_geometry(const test_geometry_t *geometry) {
288     test_define_maps[TEST_DEFINE_MAP_GEOMETRY] = (test_define_map_t){
289             geometry->defines, TEST_GEOMETRY_DEFINE_COUNT};
290 }
291 
292 // override updates
293 typedef struct test_override {
294     const char *name;
295     const intmax_t *defines;
296     size_t permutations;
297 } test_override_t;
298 
299 const test_override_t *test_overrides = NULL;
300 size_t test_override_count = 0;
301 
302 test_define_t *test_override_defines = NULL;
303 size_t test_override_define_count = 0;
304 size_t test_override_define_permutations = 1;
305 size_t test_override_define_capacity = 0;
306 
307 // suite/perm updates
test_define_suite(const struct test_suite * suite)308 void test_define_suite(const struct test_suite *suite) {
309     test_define_names[TEST_DEFINE_NAMES_SUITE] = (test_define_names_t){
310             suite->define_names, suite->define_count};
311 
312     // make sure our cache is large enough
313     if (lfs_max(suite->define_count, TEST_IMPLICIT_DEFINE_COUNT)
314             > test_define_cache_count) {
315         // align to power of two to avoid any superlinear growth
316         size_t ncount = 1 << lfs_npw2(
317                 lfs_max(suite->define_count, TEST_IMPLICIT_DEFINE_COUNT));
318         test_define_cache = realloc(test_define_cache, ncount*sizeof(intmax_t));
319         test_define_cache_mask = realloc(test_define_cache_mask,
320                 sizeof(unsigned)*(
321                     (ncount+(8*sizeof(unsigned))-1)
322                     / (8*sizeof(unsigned))));
323         test_define_cache_count = ncount;
324     }
325 
326     // map any overrides
327     if (test_override_count > 0) {
328         // first figure out the total size of override permutations
329         size_t count = 0;
330         size_t permutations = 1;
331         for (size_t i = 0; i < test_override_count; i++) {
332             for (size_t d = 0;
333                     d < lfs_max(
334                         suite->define_count,
335                         TEST_IMPLICIT_DEFINE_COUNT);
336                     d++) {
337                 // define name match?
338                 const char *name = test_define_name(d);
339                 if (name && strcmp(name, test_overrides[i].name) == 0) {
340                     count = lfs_max(count, d+1);
341                     permutations *= test_overrides[i].permutations;
342                     break;
343                 }
344             }
345         }
346         test_override_define_count = count;
347         test_override_define_permutations = permutations;
348 
349         // make sure our override arrays are big enough
350         if (count * permutations > test_override_define_capacity) {
351             // align to power of two to avoid any superlinear growth
352             size_t ncapacity = 1 << lfs_npw2(count * permutations);
353             test_override_defines = realloc(
354                     test_override_defines,
355                     sizeof(test_define_t)*ncapacity);
356             test_override_define_capacity = ncapacity;
357         }
358 
359         // zero unoverridden defines
360         memset(test_override_defines, 0,
361                 sizeof(test_define_t) * count * permutations);
362 
363         // compute permutations
364         size_t p = 1;
365         for (size_t i = 0; i < test_override_count; i++) {
366             for (size_t d = 0;
367                     d < lfs_max(
368                         suite->define_count,
369                         TEST_IMPLICIT_DEFINE_COUNT);
370                     d++) {
371                 // define name match?
372                 const char *name = test_define_name(d);
373                 if (name && strcmp(name, test_overrides[i].name) == 0) {
374                     // scatter the define permutations based on already
375                     // seen permutations
376                     for (size_t j = 0; j < permutations; j++) {
377                         test_override_defines[j*count + d] = TEST_LIT(
378                                 test_overrides[i].defines[(j/p)
379                                     % test_overrides[i].permutations]);
380                     }
381 
382                     // keep track of how many permutations we've seen so far
383                     p *= test_overrides[i].permutations;
384                     break;
385                 }
386             }
387         }
388     }
389 }
390 
test_define_perm(const struct test_suite * suite,const struct test_case * case_,size_t perm)391 void test_define_perm(
392         const struct test_suite *suite,
393         const struct test_case *case_,
394         size_t perm) {
395     if (case_->defines) {
396         test_define_maps[TEST_DEFINE_MAP_PERMUTATION] = (test_define_map_t){
397                 case_->defines + perm*suite->define_count,
398                 suite->define_count};
399     } else {
400         test_define_maps[TEST_DEFINE_MAP_PERMUTATION] = (test_define_map_t){
401                 NULL, 0};
402     }
403 }
404 
test_define_override(size_t perm)405 void test_define_override(size_t perm) {
406     test_define_maps[TEST_DEFINE_MAP_OVERRIDE] = (test_define_map_t){
407             test_override_defines + perm*test_override_define_count,
408             test_override_define_count};
409 }
410 
test_define_explicit(const test_define_t * defines,size_t define_count)411 void test_define_explicit(
412         const test_define_t *defines,
413         size_t define_count) {
414     test_define_maps[TEST_DEFINE_MAP_EXPLICIT] = (test_define_map_t){
415             defines, define_count};
416 }
417 
test_define_cleanup(void)418 void test_define_cleanup(void) {
419     // test define management can allocate a few things
420     free(test_define_cache);
421     free(test_define_cache_mask);
422     free(test_override_defines);
423 }
424 
425 
426 
427 // test state
428 extern const test_geometry_t *test_geometries;
429 extern size_t test_geometry_count;
430 
431 extern const test_powerloss_t *test_powerlosses;
432 extern size_t test_powerloss_count;
433 
434 const test_id_t *test_ids = (const test_id_t[]) {
435     {NULL, NULL, 0, NULL, 0},
436 };
437 size_t test_id_count = 1;
438 
439 size_t test_step_start = 0;
440 size_t test_step_stop = -1;
441 size_t test_step_step = 1;
442 
443 const char *test_disk_path = NULL;
444 const char *test_trace_path = NULL;
445 bool test_trace_backtrace = false;
446 uint32_t test_trace_period = 0;
447 uint32_t test_trace_freq = 0;
448 FILE *test_trace_file = NULL;
449 uint32_t test_trace_cycles = 0;
450 uint64_t test_trace_time = 0;
451 uint64_t test_trace_open_time = 0;
452 lfs_emubd_sleep_t test_read_sleep = 0.0;
453 lfs_emubd_sleep_t test_prog_sleep = 0.0;
454 lfs_emubd_sleep_t test_erase_sleep = 0.0;
455 
456 // this determines both the backtrace buffer and the trace printf buffer, if
457 // trace ends up interleaved or truncated this may need to be increased
458 #ifndef TEST_TRACE_BACKTRACE_BUFFER_SIZE
459 #define TEST_TRACE_BACKTRACE_BUFFER_SIZE 8192
460 #endif
461 void *test_trace_backtrace_buffer[
462     TEST_TRACE_BACKTRACE_BUFFER_SIZE / sizeof(void*)];
463 
464 // trace printing
test_trace(const char * fmt,...)465 void test_trace(const char *fmt, ...) {
466     if (test_trace_path) {
467         // sample at a specific period?
468         if (test_trace_period) {
469             if (test_trace_cycles % test_trace_period != 0) {
470                 test_trace_cycles += 1;
471                 return;
472             }
473             test_trace_cycles += 1;
474         }
475 
476         // sample at a specific frequency?
477         if (test_trace_freq) {
478             struct timespec t;
479             clock_gettime(CLOCK_MONOTONIC, &t);
480             uint64_t now = (uint64_t)t.tv_sec*1000*1000*1000
481                     + (uint64_t)t.tv_nsec;
482             if (now - test_trace_time < (1000*1000*1000) / test_trace_freq) {
483                 return;
484             }
485             test_trace_time = now;
486         }
487 
488         if (!test_trace_file) {
489             // Tracing output is heavy and trying to open every trace
490             // call is slow, so we only try to open the trace file every
491             // so often. Note this doesn't affect successfully opened files
492             struct timespec t;
493             clock_gettime(CLOCK_MONOTONIC, &t);
494             uint64_t now = (uint64_t)t.tv_sec*1000*1000*1000
495                     + (uint64_t)t.tv_nsec;
496             if (now - test_trace_open_time < 100*1000*1000) {
497                 return;
498             }
499             test_trace_open_time = now;
500 
501             // try to open the trace file
502             int fd;
503             if (strcmp(test_trace_path, "-") == 0) {
504                 fd = dup(1);
505                 if (fd < 0) {
506                     return;
507                 }
508             } else {
509                 fd = open(
510                         test_trace_path,
511                         O_WRONLY | O_CREAT | O_APPEND | O_NONBLOCK,
512                         0666);
513                 if (fd < 0) {
514                     return;
515                 }
516                 int err = fcntl(fd, F_SETFL, O_WRONLY | O_CREAT | O_APPEND);
517                 assert(!err);
518             }
519 
520             FILE *f = fdopen(fd, "a");
521             assert(f);
522             int err = setvbuf(f, NULL, _IOFBF,
523                     TEST_TRACE_BACKTRACE_BUFFER_SIZE);
524             assert(!err);
525             test_trace_file = f;
526         }
527 
528         // print trace
529         va_list va;
530         va_start(va, fmt);
531         int res = vfprintf(test_trace_file, fmt, va);
532         va_end(va);
533         if (res < 0) {
534             fclose(test_trace_file);
535             test_trace_file = NULL;
536             return;
537         }
538 
539         if (test_trace_backtrace) {
540             // print backtrace
541             size_t count = backtrace(
542                     test_trace_backtrace_buffer,
543                     TEST_TRACE_BACKTRACE_BUFFER_SIZE);
544             // note we skip our own stack frame
545             for (size_t i = 1; i < count; i++) {
546                 res = fprintf(test_trace_file, "\tat %p\n",
547                         test_trace_backtrace_buffer[i]);
548                 if (res < 0) {
549                     fclose(test_trace_file);
550                     test_trace_file = NULL;
551                     return;
552                 }
553             }
554         }
555 
556         // flush immediately
557         fflush(test_trace_file);
558     }
559 }
560 
561 
562 // test prng
test_prng(uint32_t * state)563 uint32_t test_prng(uint32_t *state) {
564     // A simple xorshift32 generator, easily reproducible. Keep in mind
565     // determinism is much more important than actual randomness here.
566     uint32_t x = *state;
567     x ^= x << 13;
568     x ^= x >> 17;
569     x ^= x << 5;
570     *state = x;
571     return x;
572 }
573 
574 
575 // encode our permutation into a reusable id
perm_printid(const struct test_suite * suite,const struct test_case * case_,const lfs_emubd_powercycles_t * cycles,size_t cycle_count)576 static void perm_printid(
577         const struct test_suite *suite,
578         const struct test_case *case_,
579         const lfs_emubd_powercycles_t *cycles,
580         size_t cycle_count) {
581     (void)suite;
582     // case[:permutation[:powercycles]]
583     printf("%s:", case_->name);
584     for (size_t d = 0;
585             d < lfs_max(
586                 suite->define_count,
587                 TEST_IMPLICIT_DEFINE_COUNT);
588             d++) {
589         if (test_define_ispermutation(d)) {
590             leb16_print(d);
591             leb16_print(TEST_DEFINE(d));
592         }
593     }
594 
595     // only print power-cycles if any occured
596     if (cycles) {
597         printf(":");
598         for (size_t i = 0; i < cycle_count; i++) {
599             leb16_print(cycles[i]);
600         }
601     }
602 }
603 
604 
605 // a quick trie for keeping track of permutations we've seen
606 typedef struct test_seen {
607     struct test_seen_branch *branches;
608     size_t branch_count;
609     size_t branch_capacity;
610 } test_seen_t;
611 
612 struct test_seen_branch {
613     intmax_t define;
614     struct test_seen branch;
615 };
616 
test_seen_insert(test_seen_t * seen,const struct test_suite * suite,const struct test_case * case_)617 bool test_seen_insert(
618         test_seen_t *seen,
619         const struct test_suite *suite,
620         const struct test_case *case_) {
621     (void)case_;
622     bool was_seen = true;
623 
624     // use the currently set defines
625     for (size_t d = 0;
626             d < lfs_max(
627                 suite->define_count,
628                 TEST_IMPLICIT_DEFINE_COUNT);
629             d++) {
630         // treat unpermuted defines the same as 0
631         intmax_t define = test_define_ispermutation(d) ? TEST_DEFINE(d) : 0;
632 
633         // already seen?
634         struct test_seen_branch *branch = NULL;
635         for (size_t i = 0; i < seen->branch_count; i++) {
636             if (seen->branches[i].define == define) {
637                 branch = &seen->branches[i];
638                 break;
639             }
640         }
641 
642         // need to create a new node
643         if (!branch) {
644             was_seen = false;
645             branch = mappend(
646                     (void**)&seen->branches,
647                     sizeof(struct test_seen_branch),
648                     &seen->branch_count,
649                     &seen->branch_capacity);
650             branch->define = define;
651             branch->branch = (test_seen_t){NULL, 0, 0};
652         }
653 
654         seen = &branch->branch;
655     }
656 
657     return was_seen;
658 }
659 
test_seen_cleanup(test_seen_t * seen)660 void test_seen_cleanup(test_seen_t *seen) {
661     for (size_t i = 0; i < seen->branch_count; i++) {
662         test_seen_cleanup(&seen->branches[i].branch);
663     }
664     free(seen->branches);
665 }
666 
667 static void run_powerloss_none(
668         const lfs_emubd_powercycles_t *cycles,
669         size_t cycle_count,
670         const struct test_suite *suite,
671         const struct test_case *case_);
672 static void run_powerloss_cycles(
673         const lfs_emubd_powercycles_t *cycles,
674         size_t cycle_count,
675         const struct test_suite *suite,
676         const struct test_case *case_);
677 
678 // iterate through permutations in a test case
case_forperm(const struct test_suite * suite,const struct test_case * case_,const test_define_t * defines,size_t define_count,const lfs_emubd_powercycles_t * cycles,size_t cycle_count,void (* cb)(void * data,const struct test_suite * suite,const struct test_case * case_,const test_powerloss_t * powerloss),void * data)679 static void case_forperm(
680         const struct test_suite *suite,
681         const struct test_case *case_,
682         const test_define_t *defines,
683         size_t define_count,
684         const lfs_emubd_powercycles_t *cycles,
685         size_t cycle_count,
686         void (*cb)(
687             void *data,
688             const struct test_suite *suite,
689             const struct test_case *case_,
690             const test_powerloss_t *powerloss),
691         void *data) {
692     // explicit permutation?
693     if (defines) {
694         test_define_explicit(defines, define_count);
695 
696         for (size_t v = 0; v < test_override_define_permutations; v++) {
697             // define override permutation
698             test_define_override(v);
699             test_define_flush();
700 
701             // explicit powerloss cycles?
702             if (cycles) {
703                 cb(data, suite, case_, &(test_powerloss_t){
704                         .run=run_powerloss_cycles,
705                         .cycles=cycles,
706                         .cycle_count=cycle_count});
707             } else {
708                 for (size_t p = 0; p < test_powerloss_count; p++) {
709                     // skip non-reentrant tests when powerloss testing
710                     if (test_powerlosses[p].run != run_powerloss_none
711                             && !(case_->flags & TEST_REENTRANT)) {
712                         continue;
713                     }
714 
715                     cb(data, suite, case_, &test_powerlosses[p]);
716                 }
717             }
718         }
719 
720         return;
721     }
722 
723     test_seen_t seen = {NULL, 0, 0};
724 
725     for (size_t k = 0; k < case_->permutations; k++) {
726         // define permutation
727         test_define_perm(suite, case_, k);
728 
729         for (size_t v = 0; v < test_override_define_permutations; v++) {
730             // define override permutation
731             test_define_override(v);
732 
733             for (size_t g = 0; g < test_geometry_count; g++) {
734                 // define geometry
735                 test_define_geometry(&test_geometries[g]);
736                 test_define_flush();
737 
738                 // have we seen this permutation before?
739                 bool was_seen = test_seen_insert(&seen, suite, case_);
740                 if (!(k == 0 && v == 0 && g == 0) && was_seen) {
741                     continue;
742                 }
743 
744                 if (cycles) {
745                     cb(data, suite, case_, &(test_powerloss_t){
746                             .run=run_powerloss_cycles,
747                             .cycles=cycles,
748                             .cycle_count=cycle_count});
749                 } else {
750                     for (size_t p = 0; p < test_powerloss_count; p++) {
751                         // skip non-reentrant tests when powerloss testing
752                         if (test_powerlosses[p].run != run_powerloss_none
753                                 && !(case_->flags & TEST_REENTRANT)) {
754                             continue;
755                         }
756 
757                         cb(data, suite, case_, &test_powerlosses[p]);
758                     }
759                 }
760             }
761         }
762     }
763 
764     test_seen_cleanup(&seen);
765 }
766 
767 
768 // how many permutations are there actually in a test case
769 struct perm_count_state {
770     size_t total;
771     size_t filtered;
772 };
773 
perm_count(void * data,const struct test_suite * suite,const struct test_case * case_,const test_powerloss_t * powerloss)774 void perm_count(
775         void *data,
776         const struct test_suite *suite,
777         const struct test_case *case_,
778         const test_powerloss_t *powerloss) {
779     struct perm_count_state *state = data;
780     (void)suite;
781     (void)case_;
782     (void)powerloss;
783 
784     state->total += 1;
785 
786     if (case_->filter && !case_->filter()) {
787         return;
788     }
789 
790     state->filtered += 1;
791 }
792 
793 
794 // operations we can do
summary(void)795 static void summary(void) {
796     printf("%-23s  %7s %7s %7s %11s\n",
797             "", "flags", "suites", "cases", "perms");
798     size_t suites = 0;
799     size_t cases = 0;
800     test_flags_t flags = 0;
801     struct perm_count_state perms = {0, 0};
802 
803     for (size_t t = 0; t < test_id_count; t++) {
804         for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
805             test_define_suite(&test_suites[i]);
806 
807             for (size_t j = 0; j < test_suites[i].case_count; j++) {
808                 // does neither suite nor case name match?
809                 if (test_ids[t].name && !(
810                         strcmp(test_ids[t].name,
811                             test_suites[i].name) == 0
812                         || strcmp(test_ids[t].name,
813                             test_suites[i].cases[j].name) == 0)) {
814                     continue;
815                 }
816 
817                 cases += 1;
818                 case_forperm(
819                         &test_suites[i],
820                         &test_suites[i].cases[j],
821                         test_ids[t].defines,
822                         test_ids[t].define_count,
823                         test_ids[t].cycles,
824                         test_ids[t].cycle_count,
825                         perm_count,
826                         &perms);
827             }
828 
829             suites += 1;
830             flags |= test_suites[i].flags;
831         }
832     }
833 
834     char perm_buf[64];
835     sprintf(perm_buf, "%zu/%zu", perms.filtered, perms.total);
836     char flag_buf[64];
837     sprintf(flag_buf, "%s%s",
838             (flags & TEST_REENTRANT) ? "r" : "",
839             (!flags) ? "-" : "");
840     printf("%-23s  %7s %7zu %7zu %11s\n",
841             "TOTAL",
842             flag_buf,
843             suites,
844             cases,
845             perm_buf);
846 }
847 
list_suites(void)848 static void list_suites(void) {
849     // at least size so that names fit
850     unsigned name_width = 23;
851     for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
852         size_t len = strlen(test_suites[i].name);
853         if (len > name_width) {
854             name_width = len;
855         }
856     }
857     name_width = 4*((name_width+1+4-1)/4)-1;
858 
859     printf("%-*s  %7s %7s %11s\n",
860             name_width, "suite", "flags", "cases", "perms");
861     for (size_t t = 0; t < test_id_count; t++) {
862         for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
863             test_define_suite(&test_suites[i]);
864 
865             size_t cases = 0;
866             struct perm_count_state perms = {0, 0};
867 
868             for (size_t j = 0; j < test_suites[i].case_count; j++) {
869                 // does neither suite nor case name match?
870                 if (test_ids[t].name && !(
871                         strcmp(test_ids[t].name,
872                             test_suites[i].name) == 0
873                         || strcmp(test_ids[t].name,
874                             test_suites[i].cases[j].name) == 0)) {
875                     continue;
876                 }
877 
878                 cases += 1;
879                 case_forperm(
880                         &test_suites[i],
881                         &test_suites[i].cases[j],
882                         test_ids[t].defines,
883                         test_ids[t].define_count,
884                         test_ids[t].cycles,
885                         test_ids[t].cycle_count,
886                         perm_count,
887                         &perms);
888             }
889 
890             // no tests found?
891             if (!cases) {
892                 continue;
893             }
894 
895             char perm_buf[64];
896             sprintf(perm_buf, "%zu/%zu", perms.filtered, perms.total);
897             char flag_buf[64];
898             sprintf(flag_buf, "%s%s",
899                     (test_suites[i].flags & TEST_REENTRANT) ? "r" : "",
900                     (!test_suites[i].flags) ? "-" : "");
901             printf("%-*s  %7s %7zu %11s\n",
902                     name_width,
903                     test_suites[i].name,
904                     flag_buf,
905                     cases,
906                     perm_buf);
907         }
908     }
909 }
910 
list_cases(void)911 static void list_cases(void) {
912     // at least size so that names fit
913     unsigned name_width = 23;
914     for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
915         for (size_t j = 0; j < test_suites[i].case_count; j++) {
916             size_t len = strlen(test_suites[i].cases[j].name);
917             if (len > name_width) {
918                 name_width = len;
919             }
920         }
921     }
922     name_width = 4*((name_width+1+4-1)/4)-1;
923 
924     printf("%-*s  %7s %11s\n", name_width, "case", "flags", "perms");
925     for (size_t t = 0; t < test_id_count; t++) {
926         for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
927             test_define_suite(&test_suites[i]);
928 
929             for (size_t j = 0; j < test_suites[i].case_count; j++) {
930                 // does neither suite nor case name match?
931                 if (test_ids[t].name && !(
932                         strcmp(test_ids[t].name,
933                             test_suites[i].name) == 0
934                         || strcmp(test_ids[t].name,
935                             test_suites[i].cases[j].name) == 0)) {
936                     continue;
937                 }
938 
939                 struct perm_count_state perms = {0, 0};
940                 case_forperm(
941                         &test_suites[i],
942                         &test_suites[i].cases[j],
943                         test_ids[t].defines,
944                         test_ids[t].define_count,
945                         test_ids[t].cycles,
946                         test_ids[t].cycle_count,
947                         perm_count,
948                         &perms);
949 
950                 char perm_buf[64];
951                 sprintf(perm_buf, "%zu/%zu", perms.filtered, perms.total);
952                 char flag_buf[64];
953                 sprintf(flag_buf, "%s%s",
954                         (test_suites[i].cases[j].flags & TEST_REENTRANT)
955                             ? "r" : "",
956                         (!test_suites[i].cases[j].flags)
957                             ? "-" : "");
958                 printf("%-*s  %7s %11s\n",
959                         name_width,
960                         test_suites[i].cases[j].name,
961                         flag_buf,
962                         perm_buf);
963             }
964         }
965     }
966 }
967 
list_suite_paths(void)968 static void list_suite_paths(void) {
969     // at least size so that names fit
970     unsigned name_width = 23;
971     for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
972         size_t len = strlen(test_suites[i].name);
973         if (len > name_width) {
974             name_width = len;
975         }
976     }
977     name_width = 4*((name_width+1+4-1)/4)-1;
978 
979     printf("%-*s  %s\n", name_width, "suite", "path");
980     for (size_t t = 0; t < test_id_count; t++) {
981         for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
982             size_t cases = 0;
983 
984             for (size_t j = 0; j < test_suites[i].case_count; j++) {
985                 // does neither suite nor case name match?
986                 if (test_ids[t].name && !(
987                         strcmp(test_ids[t].name,
988                             test_suites[i].name) == 0
989                         || strcmp(test_ids[t].name,
990                             test_suites[i].cases[j].name) == 0)) {
991                     continue;
992                 }
993 
994                 cases += 1;
995             }
996 
997             // no tests found?
998             if (!cases) {
999                 continue;
1000             }
1001 
1002             printf("%-*s  %s\n",
1003                     name_width,
1004                     test_suites[i].name,
1005                     test_suites[i].path);
1006         }
1007     }
1008 }
1009 
list_case_paths(void)1010 static void list_case_paths(void) {
1011     // at least size so that names fit
1012     unsigned name_width = 23;
1013     for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
1014         for (size_t j = 0; j < test_suites[i].case_count; j++) {
1015             size_t len = strlen(test_suites[i].cases[j].name);
1016             if (len > name_width) {
1017                 name_width = len;
1018             }
1019         }
1020     }
1021     name_width = 4*((name_width+1+4-1)/4)-1;
1022 
1023     printf("%-*s  %s\n", name_width, "case", "path");
1024     for (size_t t = 0; t < test_id_count; t++) {
1025         for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
1026             for (size_t j = 0; j < test_suites[i].case_count; j++) {
1027                 // does neither suite nor case name match?
1028                 if (test_ids[t].name && !(
1029                         strcmp(test_ids[t].name,
1030                             test_suites[i].name) == 0
1031                         || strcmp(test_ids[t].name,
1032                             test_suites[i].cases[j].name) == 0)) {
1033                     continue;
1034                 }
1035 
1036                 printf("%-*s  %s\n",
1037                         name_width,
1038                         test_suites[i].cases[j].name,
1039                         test_suites[i].cases[j].path);
1040             }
1041         }
1042     }
1043 }
1044 
1045 struct list_defines_define {
1046     const char *name;
1047     intmax_t *values;
1048     size_t value_count;
1049     size_t value_capacity;
1050 };
1051 
1052 struct list_defines_defines {
1053     struct list_defines_define *defines;
1054     size_t define_count;
1055     size_t define_capacity;
1056 };
1057 
list_defines_add(struct list_defines_defines * defines,size_t d)1058 static void list_defines_add(
1059         struct list_defines_defines *defines,
1060         size_t d) {
1061     const char *name = test_define_name(d);
1062     intmax_t value = TEST_DEFINE(d);
1063 
1064     // define already in defines?
1065     for (size_t i = 0; i < defines->define_count; i++) {
1066         if (strcmp(defines->defines[i].name, name) == 0) {
1067             // value already in values?
1068             for (size_t j = 0; j < defines->defines[i].value_count; j++) {
1069                 if (defines->defines[i].values[j] == value) {
1070                     return;
1071                 }
1072             }
1073 
1074             *(intmax_t*)mappend(
1075                 (void**)&defines->defines[i].values,
1076                 sizeof(intmax_t),
1077                 &defines->defines[i].value_count,
1078                 &defines->defines[i].value_capacity) = value;
1079 
1080             return;
1081         }
1082     }
1083 
1084     // new define?
1085     struct list_defines_define *define = mappend(
1086             (void**)&defines->defines,
1087             sizeof(struct list_defines_define),
1088             &defines->define_count,
1089             &defines->define_capacity);
1090     define->name = name;
1091     define->values = malloc(sizeof(intmax_t));
1092     define->values[0] = value;
1093     define->value_count = 1;
1094     define->value_capacity = 1;
1095 }
1096 
perm_list_defines(void * data,const struct test_suite * suite,const struct test_case * case_,const test_powerloss_t * powerloss)1097 void perm_list_defines(
1098         void *data,
1099         const struct test_suite *suite,
1100         const struct test_case *case_,
1101         const test_powerloss_t *powerloss) {
1102     struct list_defines_defines *defines = data;
1103     (void)suite;
1104     (void)case_;
1105     (void)powerloss;
1106 
1107     // collect defines
1108     for (size_t d = 0;
1109             d < lfs_max(suite->define_count,
1110                 TEST_IMPLICIT_DEFINE_COUNT);
1111             d++) {
1112         if (d < TEST_IMPLICIT_DEFINE_COUNT
1113                 || test_define_ispermutation(d)) {
1114             list_defines_add(defines, d);
1115         }
1116     }
1117 }
1118 
perm_list_permutation_defines(void * data,const struct test_suite * suite,const struct test_case * case_,const test_powerloss_t * powerloss)1119 void perm_list_permutation_defines(
1120         void *data,
1121         const struct test_suite *suite,
1122         const struct test_case *case_,
1123         const test_powerloss_t *powerloss) {
1124     struct list_defines_defines *defines = data;
1125     (void)suite;
1126     (void)case_;
1127     (void)powerloss;
1128 
1129     // collect permutation_defines
1130     for (size_t d = 0;
1131             d < lfs_max(suite->define_count,
1132                 TEST_IMPLICIT_DEFINE_COUNT);
1133             d++) {
1134         if (test_define_ispermutation(d)) {
1135             list_defines_add(defines, d);
1136         }
1137     }
1138 }
1139 
1140 extern const test_geometry_t builtin_geometries[];
1141 
list_defines(void)1142 static void list_defines(void) {
1143     struct list_defines_defines defines = {NULL, 0, 0};
1144 
1145     // add defines
1146     for (size_t t = 0; t < test_id_count; t++) {
1147         for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
1148             test_define_suite(&test_suites[i]);
1149 
1150             for (size_t j = 0; j < test_suites[i].case_count; j++) {
1151                 // does neither suite nor case name match?
1152                 if (test_ids[t].name && !(
1153                         strcmp(test_ids[t].name,
1154                             test_suites[i].name) == 0
1155                         || strcmp(test_ids[t].name,
1156                             test_suites[i].cases[j].name) == 0)) {
1157                     continue;
1158                 }
1159 
1160                 case_forperm(
1161                         &test_suites[i],
1162                         &test_suites[i].cases[j],
1163                         test_ids[t].defines,
1164                         test_ids[t].define_count,
1165                         test_ids[t].cycles,
1166                         test_ids[t].cycle_count,
1167                         perm_list_defines,
1168                         &defines);
1169             }
1170         }
1171     }
1172 
1173     for (size_t i = 0; i < defines.define_count; i++) {
1174         printf("%s=", defines.defines[i].name);
1175         for (size_t j = 0; j < defines.defines[i].value_count; j++) {
1176             printf("%jd", defines.defines[i].values[j]);
1177             if (j != defines.defines[i].value_count-1) {
1178                 printf(",");
1179             }
1180         }
1181         printf("\n");
1182     }
1183 
1184     for (size_t i = 0; i < defines.define_count; i++) {
1185         free(defines.defines[i].values);
1186     }
1187     free(defines.defines);
1188 }
1189 
list_permutation_defines(void)1190 static void list_permutation_defines(void) {
1191     struct list_defines_defines defines = {NULL, 0, 0};
1192 
1193     // add permutation defines
1194     for (size_t t = 0; t < test_id_count; t++) {
1195         for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
1196             test_define_suite(&test_suites[i]);
1197 
1198             for (size_t j = 0; j < test_suites[i].case_count; j++) {
1199                 // does neither suite nor case name match?
1200                 if (test_ids[t].name && !(
1201                         strcmp(test_ids[t].name,
1202                             test_suites[i].name) == 0
1203                         || strcmp(test_ids[t].name,
1204                             test_suites[i].cases[j].name) == 0)) {
1205                     continue;
1206                 }
1207 
1208                 case_forperm(
1209                         &test_suites[i],
1210                         &test_suites[i].cases[j],
1211                         test_ids[t].defines,
1212                         test_ids[t].define_count,
1213                         test_ids[t].cycles,
1214                         test_ids[t].cycle_count,
1215                         perm_list_permutation_defines,
1216                         &defines);
1217             }
1218         }
1219     }
1220 
1221     for (size_t i = 0; i < defines.define_count; i++) {
1222         printf("%s=", defines.defines[i].name);
1223         for (size_t j = 0; j < defines.defines[i].value_count; j++) {
1224             printf("%jd", defines.defines[i].values[j]);
1225             if (j != defines.defines[i].value_count-1) {
1226                 printf(",");
1227             }
1228         }
1229         printf("\n");
1230     }
1231 
1232     for (size_t i = 0; i < defines.define_count; i++) {
1233         free(defines.defines[i].values);
1234     }
1235     free(defines.defines);
1236 }
1237 
list_implicit_defines(void)1238 static void list_implicit_defines(void) {
1239     struct list_defines_defines defines = {NULL, 0, 0};
1240 
1241     // yes we do need to define a suite, this does a bit of bookeeping
1242     // such as setting up the define cache
1243     test_define_suite(&(const struct test_suite){0});
1244 
1245     // make sure to include builtin geometries here
1246     extern const test_geometry_t builtin_geometries[];
1247     for (size_t g = 0; builtin_geometries[g].name; g++) {
1248         test_define_geometry(&builtin_geometries[g]);
1249         test_define_flush();
1250 
1251         // add implicit defines
1252         for (size_t d = 0; d < TEST_IMPLICIT_DEFINE_COUNT; d++) {
1253             list_defines_add(&defines, d);
1254         }
1255     }
1256 
1257     for (size_t i = 0; i < defines.define_count; i++) {
1258         printf("%s=", defines.defines[i].name);
1259         for (size_t j = 0; j < defines.defines[i].value_count; j++) {
1260             printf("%jd", defines.defines[i].values[j]);
1261             if (j != defines.defines[i].value_count-1) {
1262                 printf(",");
1263             }
1264         }
1265         printf("\n");
1266     }
1267 
1268     for (size_t i = 0; i < defines.define_count; i++) {
1269         free(defines.defines[i].values);
1270     }
1271     free(defines.defines);
1272 }
1273 
1274 
1275 
1276 // geometries to test
1277 
1278 const test_geometry_t builtin_geometries[] = {
1279     {"default", {{0}, TEST_CONST(16),   TEST_CONST(512),   {0}}},
1280     {"eeprom",  {{0}, TEST_CONST(1),    TEST_CONST(512),   {0}}},
1281     {"emmc",    {{0}, {0},              TEST_CONST(512),   {0}}},
1282     {"nor",     {{0}, TEST_CONST(1),    TEST_CONST(4096),  {0}}},
1283     {"nand",    {{0}, TEST_CONST(4096), TEST_CONST(32768), {0}}},
1284     {NULL, {{0}, {0}, {0}, {0}}},
1285 };
1286 
1287 const test_geometry_t *test_geometries = builtin_geometries;
1288 size_t test_geometry_count = 5;
1289 
list_geometries(void)1290 static void list_geometries(void) {
1291     // at least size so that names fit
1292     unsigned name_width = 23;
1293     for (size_t g = 0; builtin_geometries[g].name; g++) {
1294         size_t len = strlen(builtin_geometries[g].name);
1295         if (len > name_width) {
1296             name_width = len;
1297         }
1298     }
1299     name_width = 4*((name_width+1+4-1)/4)-1;
1300 
1301     // yes we do need to define a suite, this does a bit of bookeeping
1302     // such as setting up the define cache
1303     test_define_suite(&(const struct test_suite){0});
1304 
1305     printf("%-*s  %7s %7s %7s %7s %11s\n",
1306             name_width, "geometry", "read", "prog", "erase", "count", "size");
1307     for (size_t g = 0; builtin_geometries[g].name; g++) {
1308         test_define_geometry(&builtin_geometries[g]);
1309         test_define_flush();
1310         printf("%-*s  %7ju %7ju %7ju %7ju %11ju\n",
1311                 name_width,
1312                 builtin_geometries[g].name,
1313                 READ_SIZE,
1314                 PROG_SIZE,
1315                 ERASE_SIZE,
1316                 ERASE_COUNT,
1317                 ERASE_SIZE*ERASE_COUNT);
1318     }
1319 }
1320 
1321 
1322 // scenarios to run tests under power-loss
1323 
run_powerloss_none(const lfs_emubd_powercycles_t * cycles,size_t cycle_count,const struct test_suite * suite,const struct test_case * case_)1324 static void run_powerloss_none(
1325         const lfs_emubd_powercycles_t *cycles,
1326         size_t cycle_count,
1327         const struct test_suite *suite,
1328         const struct test_case *case_) {
1329     (void)cycles;
1330     (void)cycle_count;
1331     (void)suite;
1332 
1333     // create block device and configuration
1334     lfs_emubd_t bd;
1335 
1336     struct lfs_config cfg = {
1337         .context            = &bd,
1338         .read               = lfs_emubd_read,
1339         .prog               = lfs_emubd_prog,
1340         .erase              = lfs_emubd_erase,
1341         .sync               = lfs_emubd_sync,
1342         .read_size          = READ_SIZE,
1343         .prog_size          = PROG_SIZE,
1344         .block_size         = BLOCK_SIZE,
1345         .block_count        = BLOCK_COUNT,
1346         .block_cycles       = BLOCK_CYCLES,
1347         .cache_size         = CACHE_SIZE,
1348         .lookahead_size     = LOOKAHEAD_SIZE,
1349         .compact_thresh     = COMPACT_THRESH,
1350         .metadata_max       = METADATA_MAX,
1351         .inline_max         = INLINE_MAX,
1352     #ifdef LFS_MULTIVERSION
1353         .disk_version       = DISK_VERSION,
1354     #endif
1355     };
1356 
1357     struct lfs_emubd_config bdcfg = {
1358         .read_size          = READ_SIZE,
1359         .prog_size          = PROG_SIZE,
1360         .erase_size         = ERASE_SIZE,
1361         .erase_count        = ERASE_COUNT,
1362         .erase_value        = ERASE_VALUE,
1363         .erase_cycles       = ERASE_CYCLES,
1364         .badblock_behavior  = BADBLOCK_BEHAVIOR,
1365         .disk_path          = test_disk_path,
1366         .read_sleep         = test_read_sleep,
1367         .prog_sleep         = test_prog_sleep,
1368         .erase_sleep        = test_erase_sleep,
1369     };
1370 
1371     int err = lfs_emubd_create(&cfg, &bdcfg);
1372     if (err) {
1373         fprintf(stderr, "error: could not create block device: %d\n", err);
1374         exit(-1);
1375     }
1376 
1377     // run the test
1378     printf("running ");
1379     perm_printid(suite, case_, NULL, 0);
1380     printf("\n");
1381 
1382     case_->run(&cfg);
1383 
1384     printf("finished ");
1385     perm_printid(suite, case_, NULL, 0);
1386     printf("\n");
1387 
1388     // cleanup
1389     err = lfs_emubd_destroy(&cfg);
1390     if (err) {
1391         fprintf(stderr, "error: could not destroy block device: %d\n", err);
1392         exit(-1);
1393     }
1394 }
1395 
powerloss_longjmp(void * c)1396 static void powerloss_longjmp(void *c) {
1397     jmp_buf *powerloss_jmp = c;
1398     longjmp(*powerloss_jmp, 1);
1399 }
1400 
run_powerloss_linear(const lfs_emubd_powercycles_t * cycles,size_t cycle_count,const struct test_suite * suite,const struct test_case * case_)1401 static void run_powerloss_linear(
1402         const lfs_emubd_powercycles_t *cycles,
1403         size_t cycle_count,
1404         const struct test_suite *suite,
1405         const struct test_case *case_) {
1406     (void)cycles;
1407     (void)cycle_count;
1408     (void)suite;
1409 
1410     // create block device and configuration
1411     lfs_emubd_t bd;
1412     jmp_buf powerloss_jmp;
1413     volatile lfs_emubd_powercycles_t i = 1;
1414 
1415     struct lfs_config cfg = {
1416         .context            = &bd,
1417         .read               = lfs_emubd_read,
1418         .prog               = lfs_emubd_prog,
1419         .erase              = lfs_emubd_erase,
1420         .sync               = lfs_emubd_sync,
1421         .read_size          = READ_SIZE,
1422         .prog_size          = PROG_SIZE,
1423         .block_size         = BLOCK_SIZE,
1424         .block_count        = BLOCK_COUNT,
1425         .block_cycles       = BLOCK_CYCLES,
1426         .cache_size         = CACHE_SIZE,
1427         .lookahead_size     = LOOKAHEAD_SIZE,
1428         .compact_thresh     = COMPACT_THRESH,
1429         .metadata_max       = METADATA_MAX,
1430         .inline_max         = INLINE_MAX,
1431     #ifdef LFS_MULTIVERSION
1432         .disk_version       = DISK_VERSION,
1433     #endif
1434     };
1435 
1436     struct lfs_emubd_config bdcfg = {
1437         .read_size          = READ_SIZE,
1438         .prog_size          = PROG_SIZE,
1439         .erase_size         = ERASE_SIZE,
1440         .erase_count        = ERASE_COUNT,
1441         .erase_value        = ERASE_VALUE,
1442         .erase_cycles       = ERASE_CYCLES,
1443         .badblock_behavior  = BADBLOCK_BEHAVIOR,
1444         .disk_path          = test_disk_path,
1445         .read_sleep         = test_read_sleep,
1446         .prog_sleep         = test_prog_sleep,
1447         .erase_sleep        = test_erase_sleep,
1448         .power_cycles       = i,
1449         .powerloss_behavior = POWERLOSS_BEHAVIOR,
1450         .powerloss_cb       = powerloss_longjmp,
1451         .powerloss_data     = &powerloss_jmp,
1452     };
1453 
1454     int err = lfs_emubd_create(&cfg, &bdcfg);
1455     if (err) {
1456         fprintf(stderr, "error: could not create block device: %d\n", err);
1457         exit(-1);
1458     }
1459 
1460     // run the test, increasing power-cycles as power-loss events occur
1461     printf("running ");
1462     perm_printid(suite, case_, NULL, 0);
1463     printf("\n");
1464 
1465     while (true) {
1466         if (!setjmp(powerloss_jmp)) {
1467             // run the test
1468             case_->run(&cfg);
1469             break;
1470         }
1471 
1472         // power-loss!
1473         printf("powerloss ");
1474         perm_printid(suite, case_, NULL, 0);
1475         printf(":");
1476         for (lfs_emubd_powercycles_t j = 1; j <= i; j++) {
1477             leb16_print(j);
1478         }
1479         printf("\n");
1480 
1481         i += 1;
1482         lfs_emubd_setpowercycles(&cfg, i);
1483     }
1484 
1485     printf("finished ");
1486     perm_printid(suite, case_, NULL, 0);
1487     printf("\n");
1488 
1489     // cleanup
1490     err = lfs_emubd_destroy(&cfg);
1491     if (err) {
1492         fprintf(stderr, "error: could not destroy block device: %d\n", err);
1493         exit(-1);
1494     }
1495 }
1496 
run_powerloss_log(const lfs_emubd_powercycles_t * cycles,size_t cycle_count,const struct test_suite * suite,const struct test_case * case_)1497 static void run_powerloss_log(
1498         const lfs_emubd_powercycles_t *cycles,
1499         size_t cycle_count,
1500         const struct test_suite *suite,
1501         const struct test_case *case_) {
1502     (void)cycles;
1503     (void)cycle_count;
1504     (void)suite;
1505 
1506     // create block device and configuration
1507     lfs_emubd_t bd;
1508     jmp_buf powerloss_jmp;
1509     volatile lfs_emubd_powercycles_t i = 1;
1510 
1511     struct lfs_config cfg = {
1512         .context            = &bd,
1513         .read               = lfs_emubd_read,
1514         .prog               = lfs_emubd_prog,
1515         .erase              = lfs_emubd_erase,
1516         .sync               = lfs_emubd_sync,
1517         .read_size          = READ_SIZE,
1518         .prog_size          = PROG_SIZE,
1519         .block_size         = BLOCK_SIZE,
1520         .block_count        = BLOCK_COUNT,
1521         .block_cycles       = BLOCK_CYCLES,
1522         .cache_size         = CACHE_SIZE,
1523         .lookahead_size     = LOOKAHEAD_SIZE,
1524         .compact_thresh     = COMPACT_THRESH,
1525         .metadata_max       = METADATA_MAX,
1526         .inline_max         = INLINE_MAX,
1527     #ifdef LFS_MULTIVERSION
1528         .disk_version       = DISK_VERSION,
1529     #endif
1530     };
1531 
1532     struct lfs_emubd_config bdcfg = {
1533         .read_size          = READ_SIZE,
1534         .prog_size          = PROG_SIZE,
1535         .erase_size         = ERASE_SIZE,
1536         .erase_count        = ERASE_COUNT,
1537         .erase_value        = ERASE_VALUE,
1538         .erase_cycles       = ERASE_CYCLES,
1539         .badblock_behavior  = BADBLOCK_BEHAVIOR,
1540         .disk_path          = test_disk_path,
1541         .read_sleep         = test_read_sleep,
1542         .prog_sleep         = test_prog_sleep,
1543         .erase_sleep        = test_erase_sleep,
1544         .power_cycles       = i,
1545         .powerloss_behavior = POWERLOSS_BEHAVIOR,
1546         .powerloss_cb       = powerloss_longjmp,
1547         .powerloss_data     = &powerloss_jmp,
1548     };
1549 
1550     int err = lfs_emubd_create(&cfg, &bdcfg);
1551     if (err) {
1552         fprintf(stderr, "error: could not create block device: %d\n", err);
1553         exit(-1);
1554     }
1555 
1556     // run the test, increasing power-cycles as power-loss events occur
1557     printf("running ");
1558     perm_printid(suite, case_, NULL, 0);
1559     printf("\n");
1560 
1561     while (true) {
1562         if (!setjmp(powerloss_jmp)) {
1563             // run the test
1564             case_->run(&cfg);
1565             break;
1566         }
1567 
1568         // power-loss!
1569         printf("powerloss ");
1570         perm_printid(suite, case_, NULL, 0);
1571         printf(":");
1572         for (lfs_emubd_powercycles_t j = 1; j <= i; j *= 2) {
1573             leb16_print(j);
1574         }
1575         printf("\n");
1576 
1577         i *= 2;
1578         lfs_emubd_setpowercycles(&cfg, i);
1579     }
1580 
1581     printf("finished ");
1582     perm_printid(suite, case_, NULL, 0);
1583     printf("\n");
1584 
1585     // cleanup
1586     err = lfs_emubd_destroy(&cfg);
1587     if (err) {
1588         fprintf(stderr, "error: could not destroy block device: %d\n", err);
1589         exit(-1);
1590     }
1591 }
1592 
run_powerloss_cycles(const lfs_emubd_powercycles_t * cycles,size_t cycle_count,const struct test_suite * suite,const struct test_case * case_)1593 static void run_powerloss_cycles(
1594         const lfs_emubd_powercycles_t *cycles,
1595         size_t cycle_count,
1596         const struct test_suite *suite,
1597         const struct test_case *case_) {
1598     (void)suite;
1599 
1600     // create block device and configuration
1601     lfs_emubd_t bd;
1602     jmp_buf powerloss_jmp;
1603     volatile size_t i = 0;
1604 
1605     struct lfs_config cfg = {
1606         .context            = &bd,
1607         .read               = lfs_emubd_read,
1608         .prog               = lfs_emubd_prog,
1609         .erase              = lfs_emubd_erase,
1610         .sync               = lfs_emubd_sync,
1611         .read_size          = READ_SIZE,
1612         .prog_size          = PROG_SIZE,
1613         .block_size         = BLOCK_SIZE,
1614         .block_count        = BLOCK_COUNT,
1615         .block_cycles       = BLOCK_CYCLES,
1616         .cache_size         = CACHE_SIZE,
1617         .lookahead_size     = LOOKAHEAD_SIZE,
1618         .compact_thresh     = COMPACT_THRESH,
1619         .metadata_max       = METADATA_MAX,
1620         .inline_max         = INLINE_MAX,
1621     #ifdef LFS_MULTIVERSION
1622         .disk_version       = DISK_VERSION,
1623     #endif
1624     };
1625 
1626     struct lfs_emubd_config bdcfg = {
1627         .read_size          = READ_SIZE,
1628         .prog_size          = PROG_SIZE,
1629         .erase_size         = ERASE_SIZE,
1630         .erase_count        = ERASE_COUNT,
1631         .erase_value        = ERASE_VALUE,
1632         .erase_cycles       = ERASE_CYCLES,
1633         .badblock_behavior  = BADBLOCK_BEHAVIOR,
1634         .disk_path          = test_disk_path,
1635         .read_sleep         = test_read_sleep,
1636         .prog_sleep         = test_prog_sleep,
1637         .erase_sleep        = test_erase_sleep,
1638         .power_cycles       = (i < cycle_count) ? cycles[i] : 0,
1639         .powerloss_behavior = POWERLOSS_BEHAVIOR,
1640         .powerloss_cb       = powerloss_longjmp,
1641         .powerloss_data     = &powerloss_jmp,
1642     };
1643 
1644     int err = lfs_emubd_create(&cfg, &bdcfg);
1645     if (err) {
1646         fprintf(stderr, "error: could not create block device: %d\n", err);
1647         exit(-1);
1648     }
1649 
1650     // run the test, increasing power-cycles as power-loss events occur
1651     printf("running ");
1652     perm_printid(suite, case_, NULL, 0);
1653     printf("\n");
1654 
1655     while (true) {
1656         if (!setjmp(powerloss_jmp)) {
1657             // run the test
1658             case_->run(&cfg);
1659             break;
1660         }
1661 
1662         // power-loss!
1663         assert(i <= cycle_count);
1664         printf("powerloss ");
1665         perm_printid(suite, case_, cycles, i+1);
1666         printf("\n");
1667 
1668         i += 1;
1669         lfs_emubd_setpowercycles(&cfg,
1670                 (i < cycle_count) ? cycles[i] : 0);
1671     }
1672 
1673     printf("finished ");
1674     perm_printid(suite, case_, NULL, 0);
1675     printf("\n");
1676 
1677     // cleanup
1678     err = lfs_emubd_destroy(&cfg);
1679     if (err) {
1680         fprintf(stderr, "error: could not destroy block device: %d\n", err);
1681         exit(-1);
1682     }
1683 }
1684 
1685 struct powerloss_exhaustive_state {
1686     struct lfs_config *cfg;
1687 
1688     lfs_emubd_t *branches;
1689     size_t branch_count;
1690     size_t branch_capacity;
1691 };
1692 
1693 struct powerloss_exhaustive_cycles {
1694     lfs_emubd_powercycles_t *cycles;
1695     size_t cycle_count;
1696     size_t cycle_capacity;
1697 };
1698 
powerloss_exhaustive_branch(void * c)1699 static void powerloss_exhaustive_branch(void *c) {
1700     struct powerloss_exhaustive_state *state = c;
1701     // append to branches
1702     lfs_emubd_t *branch = mappend(
1703             (void**)&state->branches,
1704             sizeof(lfs_emubd_t),
1705             &state->branch_count,
1706             &state->branch_capacity);
1707     if (!branch) {
1708         fprintf(stderr, "error: exhaustive: out of memory\n");
1709         exit(-1);
1710     }
1711 
1712     // create copy-on-write copy
1713     int err = lfs_emubd_copy(state->cfg, branch);
1714     if (err) {
1715         fprintf(stderr, "error: exhaustive: could not create bd copy\n");
1716         exit(-1);
1717     }
1718 
1719     // also trigger on next power cycle
1720     lfs_emubd_setpowercycles(state->cfg, 1);
1721 }
1722 
run_powerloss_exhaustive_layer(struct powerloss_exhaustive_cycles * cycles,const struct test_suite * suite,const struct test_case * case_,struct lfs_config * cfg,struct lfs_emubd_config * bdcfg,size_t depth)1723 static void run_powerloss_exhaustive_layer(
1724         struct powerloss_exhaustive_cycles *cycles,
1725         const struct test_suite *suite,
1726         const struct test_case *case_,
1727         struct lfs_config *cfg,
1728         struct lfs_emubd_config *bdcfg,
1729         size_t depth) {
1730     (void)suite;
1731 
1732     struct powerloss_exhaustive_state state = {
1733         .cfg = cfg,
1734         .branches = NULL,
1735         .branch_count = 0,
1736         .branch_capacity = 0,
1737     };
1738 
1739     // run through the test without additional powerlosses, collecting possible
1740     // branches as we do so
1741     lfs_emubd_setpowercycles(state.cfg, depth > 0 ? 1 : 0);
1742     bdcfg->powerloss_data = &state;
1743 
1744     // run the tests
1745     case_->run(cfg);
1746 
1747     // aggressively clean up memory here to try to keep our memory usage low
1748     int err = lfs_emubd_destroy(cfg);
1749     if (err) {
1750         fprintf(stderr, "error: could not destroy block device: %d\n", err);
1751         exit(-1);
1752     }
1753 
1754     // recurse into each branch
1755     for (size_t i = 0; i < state.branch_count; i++) {
1756         // first push and print the branch
1757         lfs_emubd_powercycles_t *cycle = mappend(
1758                 (void**)&cycles->cycles,
1759                 sizeof(lfs_emubd_powercycles_t),
1760                 &cycles->cycle_count,
1761                 &cycles->cycle_capacity);
1762         if (!cycle) {
1763             fprintf(stderr, "error: exhaustive: out of memory\n");
1764             exit(-1);
1765         }
1766         *cycle = i+1;
1767 
1768         printf("powerloss ");
1769         perm_printid(suite, case_, cycles->cycles, cycles->cycle_count);
1770         printf("\n");
1771 
1772         // now recurse
1773         cfg->context = &state.branches[i];
1774         run_powerloss_exhaustive_layer(cycles,
1775                 suite, case_,
1776                 cfg, bdcfg, depth-1);
1777 
1778         // pop the cycle
1779         cycles->cycle_count -= 1;
1780     }
1781 
1782     // clean up memory
1783     free(state.branches);
1784 }
1785 
run_powerloss_exhaustive(const lfs_emubd_powercycles_t * cycles,size_t cycle_count,const struct test_suite * suite,const struct test_case * case_)1786 static void run_powerloss_exhaustive(
1787         const lfs_emubd_powercycles_t *cycles,
1788         size_t cycle_count,
1789         const struct test_suite *suite,
1790         const struct test_case *case_) {
1791     (void)cycles;
1792     (void)suite;
1793 
1794     // create block device and configuration
1795     lfs_emubd_t bd;
1796 
1797     struct lfs_config cfg = {
1798         .context            = &bd,
1799         .read               = lfs_emubd_read,
1800         .prog               = lfs_emubd_prog,
1801         .erase              = lfs_emubd_erase,
1802         .sync               = lfs_emubd_sync,
1803         .read_size          = READ_SIZE,
1804         .prog_size          = PROG_SIZE,
1805         .block_size         = BLOCK_SIZE,
1806         .block_count        = BLOCK_COUNT,
1807         .block_cycles       = BLOCK_CYCLES,
1808         .cache_size         = CACHE_SIZE,
1809         .lookahead_size     = LOOKAHEAD_SIZE,
1810         .compact_thresh     = COMPACT_THRESH,
1811         .metadata_max       = METADATA_MAX,
1812         .inline_max         = INLINE_MAX,
1813     #ifdef LFS_MULTIVERSION
1814         .disk_version       = DISK_VERSION,
1815     #endif
1816     };
1817 
1818     struct lfs_emubd_config bdcfg = {
1819         .read_size          = READ_SIZE,
1820         .prog_size          = PROG_SIZE,
1821         .erase_size         = ERASE_SIZE,
1822         .erase_count        = ERASE_COUNT,
1823         .erase_value        = ERASE_VALUE,
1824         .erase_cycles       = ERASE_CYCLES,
1825         .badblock_behavior  = BADBLOCK_BEHAVIOR,
1826         .disk_path          = test_disk_path,
1827         .read_sleep         = test_read_sleep,
1828         .prog_sleep         = test_prog_sleep,
1829         .erase_sleep        = test_erase_sleep,
1830         .powerloss_behavior = POWERLOSS_BEHAVIOR,
1831         .powerloss_cb       = powerloss_exhaustive_branch,
1832         .powerloss_data     = NULL,
1833     };
1834 
1835     int err = lfs_emubd_create(&cfg, &bdcfg);
1836     if (err) {
1837         fprintf(stderr, "error: could not create block device: %d\n", err);
1838         exit(-1);
1839     }
1840 
1841     // run the test, increasing power-cycles as power-loss events occur
1842     printf("running ");
1843     perm_printid(suite, case_, NULL, 0);
1844     printf("\n");
1845 
1846     // recursively exhaust each layer of powerlosses
1847     run_powerloss_exhaustive_layer(
1848             &(struct powerloss_exhaustive_cycles){NULL, 0, 0},
1849             suite, case_,
1850             &cfg, &bdcfg, cycle_count);
1851 
1852     printf("finished ");
1853     perm_printid(suite, case_, NULL, 0);
1854     printf("\n");
1855 }
1856 
1857 
1858 const test_powerloss_t builtin_powerlosses[] = {
1859     {"none",       run_powerloss_none,       NULL, 0},
1860     {"log",        run_powerloss_log,        NULL, 0},
1861     {"linear",     run_powerloss_linear,     NULL, 0},
1862     {"exhaustive", run_powerloss_exhaustive, NULL, SIZE_MAX},
1863     {NULL, NULL, NULL, 0},
1864 };
1865 
1866 const char *const builtin_powerlosses_help[] = {
1867     "Run with no power-losses.",
1868     "Run with exponentially-decreasing power-losses.",
1869     "Run with linearly-decreasing power-losses.",
1870     "Run a all permutations of power-losses, this may take a while.",
1871     "Run a all permutations of n power-losses.",
1872     "Run a custom comma-separated set of power-losses.",
1873     "Run a custom leb16-encoded set of power-losses.",
1874 };
1875 
1876 // default to -Pnone,linear, which provides a good heuristic while still
1877 // running quickly
1878 const test_powerloss_t *test_powerlosses = (const test_powerloss_t[]){
1879     {"none",   run_powerloss_none,   NULL, 0},
1880     {"linear", run_powerloss_linear, NULL, 0},
1881 };
1882 size_t test_powerloss_count = 2;
1883 
list_powerlosses(void)1884 static void list_powerlosses(void) {
1885     // at least size so that names fit
1886     unsigned name_width = 23;
1887     for (size_t i = 0; builtin_powerlosses[i].name; i++) {
1888         size_t len = strlen(builtin_powerlosses[i].name);
1889         if (len > name_width) {
1890             name_width = len;
1891         }
1892     }
1893     name_width = 4*((name_width+1+4-1)/4)-1;
1894 
1895     printf("%-*s %s\n", name_width, "scenario", "description");
1896     size_t i = 0;
1897     for (; builtin_powerlosses[i].name; i++) {
1898         printf("%-*s %s\n",
1899                 name_width,
1900                 builtin_powerlosses[i].name,
1901                 builtin_powerlosses_help[i]);
1902     }
1903 
1904     // a couple more options with special parsing
1905     printf("%-*s %s\n", name_width, "1,2,3",   builtin_powerlosses_help[i+0]);
1906     printf("%-*s %s\n", name_width, "{1,2,3}", builtin_powerlosses_help[i+1]);
1907     printf("%-*s %s\n", name_width, ":1248g1", builtin_powerlosses_help[i+2]);
1908 }
1909 
1910 
1911 // global test step count
1912 size_t test_step = 0;
1913 
perm_run(void * data,const struct test_suite * suite,const struct test_case * case_,const test_powerloss_t * powerloss)1914 void perm_run(
1915         void *data,
1916         const struct test_suite *suite,
1917         const struct test_case *case_,
1918         const test_powerloss_t *powerloss) {
1919     (void)data;
1920 
1921     // skip this step?
1922     if (!(test_step >= test_step_start
1923             && test_step < test_step_stop
1924             && (test_step-test_step_start) % test_step_step == 0)) {
1925         test_step += 1;
1926         return;
1927     }
1928     test_step += 1;
1929 
1930     // filter?
1931     if (case_->filter && !case_->filter()) {
1932         printf("skipped ");
1933         perm_printid(suite, case_, NULL, 0);
1934         printf("\n");
1935         return;
1936     }
1937 
1938     powerloss->run(
1939             powerloss->cycles, powerloss->cycle_count,
1940             suite, case_);
1941 }
1942 
run(void)1943 static void run(void) {
1944     // ignore disconnected pipes
1945     signal(SIGPIPE, SIG_IGN);
1946 
1947     for (size_t t = 0; t < test_id_count; t++) {
1948         for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
1949             test_define_suite(&test_suites[i]);
1950 
1951             for (size_t j = 0; j < test_suites[i].case_count; j++) {
1952                 // does neither suite nor case name match?
1953                 if (test_ids[t].name && !(
1954                         strcmp(test_ids[t].name,
1955                             test_suites[i].name) == 0
1956                         || strcmp(test_ids[t].name,
1957                             test_suites[i].cases[j].name) == 0)) {
1958                     continue;
1959                 }
1960 
1961                 case_forperm(
1962                         &test_suites[i],
1963                         &test_suites[i].cases[j],
1964                         test_ids[t].defines,
1965                         test_ids[t].define_count,
1966                         test_ids[t].cycles,
1967                         test_ids[t].cycle_count,
1968                         perm_run,
1969                         NULL);
1970             }
1971         }
1972     }
1973 }
1974 
1975 
1976 
1977 // option handling
1978 enum opt_flags {
1979     OPT_HELP                     = 'h',
1980     OPT_SUMMARY                  = 'Y',
1981     OPT_LIST_SUITES              = 'l',
1982     OPT_LIST_CASES               = 'L',
1983     OPT_LIST_SUITE_PATHS         = 1,
1984     OPT_LIST_CASE_PATHS          = 2,
1985     OPT_LIST_DEFINES             = 3,
1986     OPT_LIST_PERMUTATION_DEFINES = 4,
1987     OPT_LIST_IMPLICIT_DEFINES    = 5,
1988     OPT_LIST_GEOMETRIES          = 6,
1989     OPT_LIST_POWERLOSSES         = 7,
1990     OPT_DEFINE                   = 'D',
1991     OPT_GEOMETRY                 = 'G',
1992     OPT_POWERLOSS                = 'P',
1993     OPT_STEP                     = 's',
1994     OPT_DISK                     = 'd',
1995     OPT_TRACE                    = 't',
1996     OPT_TRACE_BACKTRACE          = 8,
1997     OPT_TRACE_PERIOD             = 9,
1998     OPT_TRACE_FREQ               = 10,
1999     OPT_READ_SLEEP               = 11,
2000     OPT_PROG_SLEEP               = 12,
2001     OPT_ERASE_SLEEP              = 13,
2002 };
2003 
2004 const char *short_opts = "hYlLD:G:P:s:d:t:";
2005 
2006 const struct option long_opts[] = {
2007     {"help",             no_argument,       NULL, OPT_HELP},
2008     {"summary",          no_argument,       NULL, OPT_SUMMARY},
2009     {"list-suites",      no_argument,       NULL, OPT_LIST_SUITES},
2010     {"list-cases",       no_argument,       NULL, OPT_LIST_CASES},
2011     {"list-suite-paths", no_argument,       NULL, OPT_LIST_SUITE_PATHS},
2012     {"list-case-paths",  no_argument,       NULL, OPT_LIST_CASE_PATHS},
2013     {"list-defines",     no_argument,       NULL, OPT_LIST_DEFINES},
2014     {"list-permutation-defines",
2015                          no_argument,       NULL, OPT_LIST_PERMUTATION_DEFINES},
2016     {"list-implicit-defines",
2017                          no_argument,       NULL, OPT_LIST_IMPLICIT_DEFINES},
2018     {"list-geometries",  no_argument,       NULL, OPT_LIST_GEOMETRIES},
2019     {"list-powerlosses", no_argument,       NULL, OPT_LIST_POWERLOSSES},
2020     {"define",           required_argument, NULL, OPT_DEFINE},
2021     {"geometry",         required_argument, NULL, OPT_GEOMETRY},
2022     {"powerloss",        required_argument, NULL, OPT_POWERLOSS},
2023     {"step",             required_argument, NULL, OPT_STEP},
2024     {"disk",             required_argument, NULL, OPT_DISK},
2025     {"trace",            required_argument, NULL, OPT_TRACE},
2026     {"trace-backtrace",  no_argument,       NULL, OPT_TRACE_BACKTRACE},
2027     {"trace-period",     required_argument, NULL, OPT_TRACE_PERIOD},
2028     {"trace-freq",       required_argument, NULL, OPT_TRACE_FREQ},
2029     {"read-sleep",       required_argument, NULL, OPT_READ_SLEEP},
2030     {"prog-sleep",       required_argument, NULL, OPT_PROG_SLEEP},
2031     {"erase-sleep",      required_argument, NULL, OPT_ERASE_SLEEP},
2032     {NULL, 0, NULL, 0},
2033 };
2034 
2035 const char *const help_text[] = {
2036     "Show this help message.",
2037     "Show quick summary.",
2038     "List test suites.",
2039     "List test cases.",
2040     "List the path for each test suite.",
2041     "List the path and line number for each test case.",
2042     "List all defines in this test-runner.",
2043     "List explicit defines in this test-runner.",
2044     "List implicit defines in this test-runner.",
2045     "List the available disk geometries.",
2046     "List the available power-loss scenarios.",
2047     "Override a test define.",
2048     "Comma-separated list of disk geometries to test.",
2049     "Comma-separated list of power-loss scenarios to test.",
2050     "Comma-separated range of test permutations to run (start,stop,step).",
2051     "Direct block device operations to this file.",
2052     "Direct trace output to this file.",
2053     "Include a backtrace with every trace statement.",
2054     "Sample trace output at this period in cycles.",
2055     "Sample trace output at this frequency in hz.",
2056     "Artificial read delay in seconds.",
2057     "Artificial prog delay in seconds.",
2058     "Artificial erase delay in seconds.",
2059 };
2060 
main(int argc,char ** argv)2061 int main(int argc, char **argv) {
2062     void (*op)(void) = run;
2063 
2064     size_t test_override_capacity = 0;
2065     size_t test_geometry_capacity = 0;
2066     size_t test_powerloss_capacity = 0;
2067     size_t test_id_capacity = 0;
2068 
2069     // parse options
2070     while (true) {
2071         int c = getopt_long(argc, argv, short_opts, long_opts, NULL);
2072         switch (c) {
2073             // generate help message
2074             case OPT_HELP: {
2075                 printf("usage: %s [options] [test_id]\n", argv[0]);
2076                 printf("\n");
2077 
2078                 printf("options:\n");
2079                 size_t i = 0;
2080                 while (long_opts[i].name) {
2081                     size_t indent;
2082                     if (long_opts[i].has_arg == no_argument) {
2083                         if (long_opts[i].val >= '0' && long_opts[i].val < 'z') {
2084                             indent = printf("  -%c, --%s ",
2085                                     long_opts[i].val,
2086                                     long_opts[i].name);
2087                         } else {
2088                             indent = printf("  --%s ",
2089                                     long_opts[i].name);
2090                         }
2091                     } else {
2092                         if (long_opts[i].val >= '0' && long_opts[i].val < 'z') {
2093                             indent = printf("  -%c %s, --%s %s ",
2094                                     long_opts[i].val,
2095                                     long_opts[i].name,
2096                                     long_opts[i].name,
2097                                     long_opts[i].name);
2098                         } else {
2099                             indent = printf("  --%s %s ",
2100                                     long_opts[i].name,
2101                                     long_opts[i].name);
2102                         }
2103                     }
2104 
2105                     // a quick, hacky, byte-level method for text wrapping
2106                     size_t len = strlen(help_text[i]);
2107                     size_t j = 0;
2108                     if (indent < 24) {
2109                         printf("%*s %.80s\n",
2110                                 (int)(24-1-indent),
2111                                 "",
2112                                 &help_text[i][j]);
2113                         j += 80;
2114                     } else {
2115                         printf("\n");
2116                     }
2117 
2118                     while (j < len) {
2119                         printf("%24s%.80s\n", "", &help_text[i][j]);
2120                         j += 80;
2121                     }
2122 
2123                     i += 1;
2124                 }
2125 
2126                 printf("\n");
2127                 exit(0);
2128             }
2129             // summary/list flags
2130             case OPT_SUMMARY:
2131                 op = summary;
2132                 break;
2133             case OPT_LIST_SUITES:
2134                 op = list_suites;
2135                 break;
2136             case OPT_LIST_CASES:
2137                 op = list_cases;
2138                 break;
2139             case OPT_LIST_SUITE_PATHS:
2140                 op = list_suite_paths;
2141                 break;
2142             case OPT_LIST_CASE_PATHS:
2143                 op = list_case_paths;
2144                 break;
2145             case OPT_LIST_DEFINES:
2146                 op = list_defines;
2147                 break;
2148             case OPT_LIST_PERMUTATION_DEFINES:
2149                 op = list_permutation_defines;
2150                 break;
2151             case OPT_LIST_IMPLICIT_DEFINES:
2152                 op = list_implicit_defines;
2153                 break;
2154             case OPT_LIST_GEOMETRIES:
2155                 op = list_geometries;
2156                 break;
2157             case OPT_LIST_POWERLOSSES:
2158                 op = list_powerlosses;
2159                 break;
2160             // configuration
2161             case OPT_DEFINE: {
2162                 // allocate space
2163                 test_override_t *override = mappend(
2164                         (void**)&test_overrides,
2165                         sizeof(test_override_t),
2166                         &test_override_count,
2167                         &test_override_capacity);
2168 
2169                 // parse into string key/intmax_t value, cannibalizing the
2170                 // arg in the process
2171                 char *sep = strchr(optarg, '=');
2172                 char *parsed = NULL;
2173                 if (!sep) {
2174                     goto invalid_define;
2175                 }
2176                 *sep = '\0';
2177                 override->name = optarg;
2178                 optarg = sep+1;
2179 
2180                 // parse comma-separated permutations
2181                 {
2182                     override->defines = NULL;
2183                     override->permutations = 0;
2184                     size_t override_capacity = 0;
2185                     while (true) {
2186                         optarg += strspn(optarg, " ");
2187 
2188                         if (strncmp(optarg, "range", strlen("range")) == 0) {
2189                             // range of values
2190                             optarg += strlen("range");
2191                             optarg += strspn(optarg, " ");
2192                             if (*optarg != '(') {
2193                                 goto invalid_define;
2194                             }
2195                             optarg += 1;
2196 
2197                             intmax_t start = strtoumax(optarg, &parsed, 0);
2198                             intmax_t stop = -1;
2199                             intmax_t step = 1;
2200                             // allow empty string for start=0
2201                             if (parsed == optarg) {
2202                                 start = 0;
2203                             }
2204                             optarg = parsed + strspn(parsed, " ");
2205 
2206                             if (*optarg != ',' && *optarg != ')') {
2207                                 goto invalid_define;
2208                             }
2209 
2210                             if (*optarg == ',') {
2211                                 optarg += 1;
2212                                 stop = strtoumax(optarg, &parsed, 0);
2213                                 // allow empty string for stop=end
2214                                 if (parsed == optarg) {
2215                                     stop = -1;
2216                                 }
2217                                 optarg = parsed + strspn(parsed, " ");
2218 
2219                                 if (*optarg != ',' && *optarg != ')') {
2220                                     goto invalid_define;
2221                                 }
2222 
2223                                 if (*optarg == ',') {
2224                                     optarg += 1;
2225                                     step = strtoumax(optarg, &parsed, 0);
2226                                     // allow empty string for stop=1
2227                                     if (parsed == optarg) {
2228                                         step = 1;
2229                                     }
2230                                     optarg = parsed + strspn(parsed, " ");
2231 
2232                                     if (*optarg != ')') {
2233                                         goto invalid_define;
2234                                     }
2235                                 }
2236                             } else {
2237                                 // single value = stop only
2238                                 stop = start;
2239                                 start = 0;
2240                             }
2241 
2242                             if (*optarg != ')') {
2243                                 goto invalid_define;
2244                             }
2245                             optarg += 1;
2246 
2247                             // calculate the range of values
2248                             assert(step != 0);
2249                             for (intmax_t i = start;
2250                                     (step < 0)
2251                                         ? i > stop
2252                                         : (uintmax_t)i < (uintmax_t)stop;
2253                                     i += step) {
2254                                 *(intmax_t*)mappend(
2255                                         (void**)&override->defines,
2256                                         sizeof(intmax_t),
2257                                         &override->permutations,
2258                                         &override_capacity) = i;
2259                             }
2260                         } else if (*optarg != '\0') {
2261                             // single value
2262                             intmax_t define = strtoimax(optarg, &parsed, 0);
2263                             if (parsed == optarg) {
2264                                 goto invalid_define;
2265                             }
2266                             optarg = parsed + strspn(parsed, " ");
2267                             *(intmax_t*)mappend(
2268                                     (void**)&override->defines,
2269                                     sizeof(intmax_t),
2270                                     &override->permutations,
2271                                     &override_capacity) = define;
2272                         } else {
2273                             break;
2274                         }
2275 
2276                         if (*optarg == ',') {
2277                             optarg += 1;
2278                         }
2279                     }
2280                 }
2281                 assert(override->permutations > 0);
2282                 break;
2283 
2284 invalid_define:
2285                 fprintf(stderr, "error: invalid define: %s\n", optarg);
2286                 exit(-1);
2287             }
2288             case OPT_GEOMETRY: {
2289                 // reset our geometry scenarios
2290                 if (test_geometry_capacity > 0) {
2291                     free((test_geometry_t*)test_geometries);
2292                 }
2293                 test_geometries = NULL;
2294                 test_geometry_count = 0;
2295                 test_geometry_capacity = 0;
2296 
2297                 // parse the comma separated list of disk geometries
2298                 while (*optarg) {
2299                     // allocate space
2300                     test_geometry_t *geometry = mappend(
2301                             (void**)&test_geometries,
2302                             sizeof(test_geometry_t),
2303                             &test_geometry_count,
2304                             &test_geometry_capacity);
2305 
2306                     // parse the disk geometry
2307                     optarg += strspn(optarg, " ");
2308 
2309                     // named disk geometry
2310                     size_t len = strcspn(optarg, " ,");
2311                     for (size_t i = 0; builtin_geometries[i].name; i++) {
2312                         if (len == strlen(builtin_geometries[i].name)
2313                                 && memcmp(optarg,
2314                                     builtin_geometries[i].name,
2315                                     len) == 0)  {
2316                             *geometry = builtin_geometries[i];
2317                             optarg += len;
2318                             goto geometry_next;
2319                         }
2320                     }
2321 
2322                     // comma-separated read/prog/erase/count
2323                     if (*optarg == '{') {
2324                         lfs_size_t sizes[4];
2325                         size_t count = 0;
2326 
2327                         char *s = optarg + 1;
2328                         while (count < 4) {
2329                             char *parsed = NULL;
2330                             sizes[count] = strtoumax(s, &parsed, 0);
2331                             count += 1;
2332 
2333                             s = parsed + strspn(parsed, " ");
2334                             if (*s == ',') {
2335                                 s += 1;
2336                                 continue;
2337                             } else if (*s == '}') {
2338                                 s += 1;
2339                                 break;
2340                             } else {
2341                                 goto geometry_unknown;
2342                             }
2343                         }
2344 
2345                         // allow implicit r=p and p=e for common geometries
2346                         memset(geometry, 0, sizeof(test_geometry_t));
2347                         if (count >= 3) {
2348                             geometry->defines[READ_SIZE_i]
2349                                     = TEST_LIT(sizes[0]);
2350                             geometry->defines[PROG_SIZE_i]
2351                                     = TEST_LIT(sizes[1]);
2352                             geometry->defines[ERASE_SIZE_i]
2353                                     = TEST_LIT(sizes[2]);
2354                         } else if (count >= 2) {
2355                             geometry->defines[PROG_SIZE_i]
2356                                     = TEST_LIT(sizes[0]);
2357                             geometry->defines[ERASE_SIZE_i]
2358                                     = TEST_LIT(sizes[1]);
2359                         } else {
2360                             geometry->defines[ERASE_SIZE_i]
2361                                     = TEST_LIT(sizes[0]);
2362                         }
2363                         if (count >= 4) {
2364                             geometry->defines[ERASE_COUNT_i]
2365                                     = TEST_LIT(sizes[3]);
2366                         }
2367                         optarg = s;
2368                         goto geometry_next;
2369                     }
2370 
2371                     // leb16-encoded read/prog/erase/count
2372                     if (*optarg == ':') {
2373                         lfs_size_t sizes[4];
2374                         size_t count = 0;
2375 
2376                         char *s = optarg + 1;
2377                         while (true) {
2378                             char *parsed = NULL;
2379                             uintmax_t x = leb16_parse(s, &parsed);
2380                             if (parsed == s || count >= 4) {
2381                                 break;
2382                             }
2383 
2384                             sizes[count] = x;
2385                             count += 1;
2386                             s = parsed;
2387                         }
2388 
2389                         // allow implicit r=p and p=e for common geometries
2390                         memset(geometry, 0, sizeof(test_geometry_t));
2391                         if (count >= 3) {
2392                             geometry->defines[READ_SIZE_i]
2393                                     = TEST_LIT(sizes[0]);
2394                             geometry->defines[PROG_SIZE_i]
2395                                     = TEST_LIT(sizes[1]);
2396                             geometry->defines[ERASE_SIZE_i]
2397                                     = TEST_LIT(sizes[2]);
2398                         } else if (count >= 2) {
2399                             geometry->defines[PROG_SIZE_i]
2400                                     = TEST_LIT(sizes[0]);
2401                             geometry->defines[ERASE_SIZE_i]
2402                                     = TEST_LIT(sizes[1]);
2403                         } else {
2404                             geometry->defines[ERASE_SIZE_i]
2405                                     = TEST_LIT(sizes[0]);
2406                         }
2407                         if (count >= 4) {
2408                             geometry->defines[ERASE_COUNT_i]
2409                                     = TEST_LIT(sizes[3]);
2410                         }
2411                         optarg = s;
2412                         goto geometry_next;
2413                     }
2414 
2415 geometry_unknown:
2416                     // unknown scenario?
2417                     fprintf(stderr, "error: unknown disk geometry: %s\n",
2418                             optarg);
2419                     exit(-1);
2420 
2421 geometry_next:
2422                     optarg += strspn(optarg, " ");
2423                     if (*optarg == ',') {
2424                         optarg += 1;
2425                     } else if (*optarg == '\0') {
2426                         break;
2427                     } else {
2428                         goto geometry_unknown;
2429                     }
2430                 }
2431                 break;
2432             }
2433             case OPT_POWERLOSS: {
2434                 // reset our powerloss scenarios
2435                 if (test_powerloss_capacity > 0) {
2436                     free((test_powerloss_t*)test_powerlosses);
2437                 }
2438                 test_powerlosses = NULL;
2439                 test_powerloss_count = 0;
2440                 test_powerloss_capacity = 0;
2441 
2442                 // parse the comma separated list of power-loss scenarios
2443                 while (*optarg) {
2444                     // allocate space
2445                     test_powerloss_t *powerloss = mappend(
2446                             (void**)&test_powerlosses,
2447                             sizeof(test_powerloss_t),
2448                             &test_powerloss_count,
2449                             &test_powerloss_capacity);
2450 
2451                     // parse the power-loss scenario
2452                     optarg += strspn(optarg, " ");
2453 
2454                     // named power-loss scenario
2455                     size_t len = strcspn(optarg, " ,");
2456                     for (size_t i = 0; builtin_powerlosses[i].name; i++) {
2457                         if (len == strlen(builtin_powerlosses[i].name)
2458                                 && memcmp(optarg,
2459                                     builtin_powerlosses[i].name,
2460                                     len) == 0) {
2461                             *powerloss = builtin_powerlosses[i];
2462                             optarg += len;
2463                             goto powerloss_next;
2464                         }
2465                     }
2466 
2467                     // comma-separated permutation
2468                     if (*optarg == '{') {
2469                         lfs_emubd_powercycles_t *cycles = NULL;
2470                         size_t cycle_count = 0;
2471                         size_t cycle_capacity = 0;
2472 
2473                         char *s = optarg + 1;
2474                         while (true) {
2475                             char *parsed = NULL;
2476                             *(lfs_emubd_powercycles_t*)mappend(
2477                                     (void**)&cycles,
2478                                     sizeof(lfs_emubd_powercycles_t),
2479                                     &cycle_count,
2480                                     &cycle_capacity)
2481                                     = strtoumax(s, &parsed, 0);
2482 
2483                             s = parsed + strspn(parsed, " ");
2484                             if (*s == ',') {
2485                                 s += 1;
2486                                 continue;
2487                             } else if (*s == '}') {
2488                                 s += 1;
2489                                 break;
2490                             } else {
2491                                 goto powerloss_unknown;
2492                             }
2493                         }
2494 
2495                         *powerloss = (test_powerloss_t){
2496                             .run = run_powerloss_cycles,
2497                             .cycles = cycles,
2498                             .cycle_count = cycle_count,
2499                         };
2500                         optarg = s;
2501                         goto powerloss_next;
2502                     }
2503 
2504                     // leb16-encoded permutation
2505                     if (*optarg == ':') {
2506                         lfs_emubd_powercycles_t *cycles = NULL;
2507                         size_t cycle_count = 0;
2508                         size_t cycle_capacity = 0;
2509 
2510                         char *s = optarg + 1;
2511                         while (true) {
2512                             char *parsed = NULL;
2513                             uintmax_t x = leb16_parse(s, &parsed);
2514                             if (parsed == s) {
2515                                 break;
2516                             }
2517 
2518                             *(lfs_emubd_powercycles_t*)mappend(
2519                                     (void**)&cycles,
2520                                     sizeof(lfs_emubd_powercycles_t),
2521                                     &cycle_count,
2522                                     &cycle_capacity) = x;
2523                             s = parsed;
2524                         }
2525 
2526                         *powerloss = (test_powerloss_t){
2527                             .run = run_powerloss_cycles,
2528                             .cycles = cycles,
2529                             .cycle_count = cycle_count,
2530                         };
2531                         optarg = s;
2532                         goto powerloss_next;
2533                     }
2534 
2535                     // exhaustive permutations
2536                     {
2537                         char *parsed = NULL;
2538                         size_t count = strtoumax(optarg, &parsed, 0);
2539                         if (parsed == optarg) {
2540                             goto powerloss_unknown;
2541                         }
2542                         *powerloss = (test_powerloss_t){
2543                             .run = run_powerloss_exhaustive,
2544                             .cycles = NULL,
2545                             .cycle_count = count,
2546                         };
2547                         optarg = (char*)parsed;
2548                         goto powerloss_next;
2549                     }
2550 
2551 powerloss_unknown:
2552                     // unknown scenario?
2553                     fprintf(stderr, "error: unknown power-loss scenario: %s\n",
2554                             optarg);
2555                     exit(-1);
2556 
2557 powerloss_next:
2558                     optarg += strspn(optarg, " ");
2559                     if (*optarg == ',') {
2560                         optarg += 1;
2561                     } else if (*optarg == '\0') {
2562                         break;
2563                     } else {
2564                         goto powerloss_unknown;
2565                     }
2566                 }
2567                 break;
2568             }
2569             case OPT_STEP: {
2570                 char *parsed = NULL;
2571                 test_step_start = strtoumax(optarg, &parsed, 0);
2572                 test_step_stop = -1;
2573                 test_step_step = 1;
2574                 // allow empty string for start=0
2575                 if (parsed == optarg) {
2576                     test_step_start = 0;
2577                 }
2578                 optarg = parsed + strspn(parsed, " ");
2579 
2580                 if (*optarg != ',' && *optarg != '\0') {
2581                     goto step_unknown;
2582                 }
2583 
2584                 if (*optarg == ',') {
2585                     optarg += 1;
2586                     test_step_stop = strtoumax(optarg, &parsed, 0);
2587                     // allow empty string for stop=end
2588                     if (parsed == optarg) {
2589                         test_step_stop = -1;
2590                     }
2591                     optarg = parsed + strspn(parsed, " ");
2592 
2593                     if (*optarg != ',' && *optarg != '\0') {
2594                         goto step_unknown;
2595                     }
2596 
2597                     if (*optarg == ',') {
2598                         optarg += 1;
2599                         test_step_step = strtoumax(optarg, &parsed, 0);
2600                         // allow empty string for stop=1
2601                         if (parsed == optarg) {
2602                             test_step_step = 1;
2603                         }
2604                         optarg = parsed + strspn(parsed, " ");
2605 
2606                         if (*optarg != '\0') {
2607                             goto step_unknown;
2608                         }
2609                     }
2610                 } else {
2611                     // single value = stop only
2612                     test_step_stop = test_step_start;
2613                     test_step_start = 0;
2614                 }
2615 
2616                 break;
2617 step_unknown:
2618                 fprintf(stderr, "error: invalid step: %s\n", optarg);
2619                 exit(-1);
2620             }
2621             case OPT_DISK:
2622                 test_disk_path = optarg;
2623                 break;
2624             case OPT_TRACE:
2625                 test_trace_path = optarg;
2626                 break;
2627             case OPT_TRACE_BACKTRACE:
2628                 test_trace_backtrace = true;
2629                 break;
2630             case OPT_TRACE_PERIOD: {
2631                 char *parsed = NULL;
2632                 test_trace_period = strtoumax(optarg, &parsed, 0);
2633                 if (parsed == optarg) {
2634                     fprintf(stderr, "error: invalid trace-period: %s\n", optarg);
2635                     exit(-1);
2636                 }
2637                 break;
2638             }
2639             case OPT_TRACE_FREQ: {
2640                 char *parsed = NULL;
2641                 test_trace_freq = strtoumax(optarg, &parsed, 0);
2642                 if (parsed == optarg) {
2643                     fprintf(stderr, "error: invalid trace-freq: %s\n", optarg);
2644                     exit(-1);
2645                 }
2646                 break;
2647             }
2648             case OPT_READ_SLEEP: {
2649                 char *parsed = NULL;
2650                 double read_sleep = strtod(optarg, &parsed);
2651                 if (parsed == optarg) {
2652                     fprintf(stderr, "error: invalid read-sleep: %s\n", optarg);
2653                     exit(-1);
2654                 }
2655                 test_read_sleep = read_sleep*1.0e9;
2656                 break;
2657             }
2658             case OPT_PROG_SLEEP: {
2659                 char *parsed = NULL;
2660                 double prog_sleep = strtod(optarg, &parsed);
2661                 if (parsed == optarg) {
2662                     fprintf(stderr, "error: invalid prog-sleep: %s\n", optarg);
2663                     exit(-1);
2664                 }
2665                 test_prog_sleep = prog_sleep*1.0e9;
2666                 break;
2667             }
2668             case OPT_ERASE_SLEEP: {
2669                 char *parsed = NULL;
2670                 double erase_sleep = strtod(optarg, &parsed);
2671                 if (parsed == optarg) {
2672                     fprintf(stderr, "error: invalid erase-sleep: %s\n", optarg);
2673                     exit(-1);
2674                 }
2675                 test_erase_sleep = erase_sleep*1.0e9;
2676                 break;
2677             }
2678             // done parsing
2679             case -1:
2680                 goto getopt_done;
2681             // unknown arg, getopt prints a message for us
2682             default:
2683                 exit(-1);
2684         }
2685     }
2686 getopt_done: ;
2687 
2688     if (argc > optind) {
2689         // reset our test identifier list
2690         test_ids = NULL;
2691         test_id_count = 0;
2692         test_id_capacity = 0;
2693     }
2694 
2695     // parse test identifier, if any, cannibalizing the arg in the process
2696     for (; argc > optind; optind++) {
2697         test_define_t *defines = NULL;
2698         size_t define_count = 0;
2699         lfs_emubd_powercycles_t *cycles = NULL;
2700         size_t cycle_count = 0;
2701 
2702         // parse name, can be suite or case
2703         char *name = argv[optind];
2704         char *defines_ = strchr(name, ':');
2705         if (defines_) {
2706             *defines_ = '\0';
2707             defines_ += 1;
2708         }
2709 
2710         // remove optional path and .toml suffix
2711         char *slash = strrchr(name, '/');
2712         if (slash) {
2713             name = slash+1;
2714         }
2715 
2716         size_t name_len = strlen(name);
2717         if (name_len > 5 && strcmp(&name[name_len-5], ".toml") == 0) {
2718             name[name_len-5] = '\0';
2719         }
2720 
2721         if (defines_) {
2722             // parse defines
2723             char *cycles_ = strchr(defines_, ':');
2724             if (cycles_) {
2725                 *cycles_ = '\0';
2726                 cycles_ += 1;
2727             }
2728 
2729             while (true) {
2730                 char *parsed;
2731                 size_t d = leb16_parse(defines_, &parsed);
2732                 intmax_t v = leb16_parse(parsed, &parsed);
2733                 if (parsed == defines_) {
2734                     break;
2735                 }
2736                 defines_ = parsed;
2737 
2738                 if (d >= define_count) {
2739                     // align to power of two to avoid any superlinear growth
2740                     size_t ncount = 1 << lfs_npw2(d+1);
2741                     defines = realloc(defines,
2742                             ncount*sizeof(test_define_t));
2743                     memset(defines+define_count, 0,
2744                             (ncount-define_count)*sizeof(test_define_t));
2745                     define_count = ncount;
2746                 }
2747                 defines[d] = TEST_LIT(v);
2748             }
2749 
2750             if (cycles_) {
2751                 // parse power cycles
2752                 size_t cycle_capacity = 0;
2753                 while (*cycles_ != '\0') {
2754                     char *parsed = NULL;
2755                     *(lfs_emubd_powercycles_t*)mappend(
2756                             (void**)&cycles,
2757                             sizeof(lfs_emubd_powercycles_t),
2758                             &cycle_count,
2759                             &cycle_capacity)
2760                             = leb16_parse(cycles_, &parsed);
2761                     if (parsed == cycles_) {
2762                         fprintf(stderr, "error: "
2763                                 "could not parse test cycles: %s\n",
2764                                 cycles_);
2765                         exit(-1);
2766                     }
2767                     cycles_ = parsed;
2768                 }
2769             }
2770         }
2771 
2772         // append to identifier list
2773         *(test_id_t*)mappend(
2774                 (void**)&test_ids,
2775                 sizeof(test_id_t),
2776                 &test_id_count,
2777                 &test_id_capacity) = (test_id_t){
2778             .name = name,
2779             .defines = defines,
2780             .define_count = define_count,
2781             .cycles = cycles,
2782             .cycle_count = cycle_count,
2783         };
2784     }
2785 
2786     // do the thing
2787     op();
2788 
2789     // cleanup (need to be done for valgrind testing)
2790     test_define_cleanup();
2791     if (test_overrides) {
2792         for (size_t i = 0; i < test_override_count; i++) {
2793             free((void*)test_overrides[i].defines);
2794         }
2795         free((void*)test_overrides);
2796     }
2797     if (test_geometry_capacity) {
2798         free((void*)test_geometries);
2799     }
2800     if (test_powerloss_capacity) {
2801         for (size_t i = 0; i < test_powerloss_count; i++) {
2802             free((void*)test_powerlosses[i].cycles);
2803         }
2804         free((void*)test_powerlosses);
2805     }
2806     if (test_id_capacity) {
2807         for (size_t i = 0; i < test_id_count; i++) {
2808             free((void*)test_ids[i].defines);
2809             free((void*)test_ids[i].cycles);
2810         }
2811         free((void*)test_ids);
2812     }
2813 }
2814