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
29void *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
54static 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
71static 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
113typedef struct test_geometry {
114    const char *name;
115    test_define_t defines[TEST_GEOMETRY_DEFINE_COUNT];
116} test_geometry_t;
117
118typedef 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
129typedef 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
139extern struct test_suite __start__test_suites;
140extern struct test_suite __stop__test_suites;
141
142const 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
148typedef struct test_define_map {
149    const test_define_t *defines;
150    size_t count;
151} test_define_map_t;
152
153typedef struct test_define_names {
154    const char *const *names;
155    size_t count;
156} test_define_names_t;
157
158intmax_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
182test_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
199test_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
212intmax_t *test_define_cache;
213size_t test_define_cache_count;
214unsigned *test_define_cache_mask;
215
216const 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
229bool 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
241intmax_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
276void 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
285const test_geometry_t *test_geometry = NULL;
286
287void 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
293typedef struct test_override {
294    const char *name;
295    const intmax_t *defines;
296    size_t permutations;
297} test_override_t;
298
299const test_override_t *test_overrides = NULL;
300size_t test_override_count = 0;
301
302test_define_t *test_override_defines = NULL;
303size_t test_override_define_count = 0;
304size_t test_override_define_permutations = 1;
305size_t test_override_define_capacity = 0;
306
307// suite/perm updates
308void 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
391void 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
405void 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
411void 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
418void 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
428extern const test_geometry_t *test_geometries;
429extern size_t test_geometry_count;
430
431extern const test_powerloss_t *test_powerlosses;
432extern size_t test_powerloss_count;
433
434const test_id_t *test_ids = (const test_id_t[]) {
435    {NULL, NULL, 0, NULL, 0},
436};
437size_t test_id_count = 1;
438
439size_t test_step_start = 0;
440size_t test_step_stop = -1;
441size_t test_step_step = 1;
442
443const char *test_disk_path = NULL;
444const char *test_trace_path = NULL;
445bool test_trace_backtrace = false;
446uint32_t test_trace_period = 0;
447uint32_t test_trace_freq = 0;
448FILE *test_trace_file = NULL;
449uint32_t test_trace_cycles = 0;
450uint64_t test_trace_time = 0;
451uint64_t test_trace_open_time = 0;
452lfs_emubd_sleep_t test_read_sleep = 0.0;
453lfs_emubd_sleep_t test_prog_sleep = 0.0;
454lfs_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
461void *test_trace_backtrace_buffer[
462    TEST_TRACE_BACKTRACE_BUFFER_SIZE / sizeof(void*)];
463
464// trace printing
465void 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
563uint32_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
576static 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
606typedef struct test_seen {
607    struct test_seen_branch *branches;
608    size_t branch_count;
609    size_t branch_capacity;
610} test_seen_t;
611
612struct test_seen_branch {
613    intmax_t define;
614    struct test_seen branch;
615};
616
617bool 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
660void 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
667static 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_);
672static 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
679static 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
769struct perm_count_state {
770    size_t total;
771    size_t filtered;
772};
773
774void 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
795static 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
848static 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
911static 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
968static 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
1010static 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
1045struct list_defines_define {
1046    const char *name;
1047    intmax_t *values;
1048    size_t value_count;
1049    size_t value_capacity;
1050};
1051
1052struct list_defines_defines {
1053    struct list_defines_define *defines;
1054    size_t define_count;
1055    size_t define_capacity;
1056};
1057
1058static 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
1097void 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
1119void 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
1140extern const test_geometry_t builtin_geometries[];
1141
1142static 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
1190static 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
1238static 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
1278const 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
1287const test_geometry_t *test_geometries = builtin_geometries;
1288size_t test_geometry_count = 5;
1289
1290static 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
1324static 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    #ifdef LFS_MULTIVERSION
1350        .disk_version       = DISK_VERSION,
1351    #endif
1352    };
1353
1354    struct lfs_emubd_config bdcfg = {
1355        .read_size          = READ_SIZE,
1356        .prog_size          = PROG_SIZE,
1357        .erase_size         = ERASE_SIZE,
1358        .erase_count        = ERASE_COUNT,
1359        .erase_value        = ERASE_VALUE,
1360        .erase_cycles       = ERASE_CYCLES,
1361        .badblock_behavior  = BADBLOCK_BEHAVIOR,
1362        .disk_path          = test_disk_path,
1363        .read_sleep         = test_read_sleep,
1364        .prog_sleep         = test_prog_sleep,
1365        .erase_sleep        = test_erase_sleep,
1366    };
1367
1368    int err = lfs_emubd_create(&cfg, &bdcfg);
1369    if (err) {
1370        fprintf(stderr, "error: could not create block device: %d\n", err);
1371        exit(-1);
1372    }
1373
1374    // run the test
1375    printf("running ");
1376    perm_printid(suite, case_, NULL, 0);
1377    printf("\n");
1378
1379    case_->run(&cfg);
1380
1381    printf("finished ");
1382    perm_printid(suite, case_, NULL, 0);
1383    printf("\n");
1384
1385    // cleanup
1386    err = lfs_emubd_destroy(&cfg);
1387    if (err) {
1388        fprintf(stderr, "error: could not destroy block device: %d\n", err);
1389        exit(-1);
1390    }
1391}
1392
1393static void powerloss_longjmp(void *c) {
1394    jmp_buf *powerloss_jmp = c;
1395    longjmp(*powerloss_jmp, 1);
1396}
1397
1398static void run_powerloss_linear(
1399        const lfs_emubd_powercycles_t *cycles,
1400        size_t cycle_count,
1401        const struct test_suite *suite,
1402        const struct test_case *case_) {
1403    (void)cycles;
1404    (void)cycle_count;
1405    (void)suite;
1406
1407    // create block device and configuration
1408    lfs_emubd_t bd;
1409    jmp_buf powerloss_jmp;
1410    volatile lfs_emubd_powercycles_t i = 1;
1411
1412    struct lfs_config cfg = {
1413        .context            = &bd,
1414        .read               = lfs_emubd_read,
1415        .prog               = lfs_emubd_prog,
1416        .erase              = lfs_emubd_erase,
1417        .sync               = lfs_emubd_sync,
1418        .read_size          = READ_SIZE,
1419        .prog_size          = PROG_SIZE,
1420        .block_size         = BLOCK_SIZE,
1421        .block_count        = BLOCK_COUNT,
1422        .block_cycles       = BLOCK_CYCLES,
1423        .cache_size         = CACHE_SIZE,
1424        .lookahead_size     = LOOKAHEAD_SIZE,
1425    #ifdef LFS_MULTIVERSION
1426        .disk_version       = DISK_VERSION,
1427    #endif
1428    };
1429
1430    struct lfs_emubd_config bdcfg = {
1431        .read_size          = READ_SIZE,
1432        .prog_size          = PROG_SIZE,
1433        .erase_size         = ERASE_SIZE,
1434        .erase_count        = ERASE_COUNT,
1435        .erase_value        = ERASE_VALUE,
1436        .erase_cycles       = ERASE_CYCLES,
1437        .badblock_behavior  = BADBLOCK_BEHAVIOR,
1438        .disk_path          = test_disk_path,
1439        .read_sleep         = test_read_sleep,
1440        .prog_sleep         = test_prog_sleep,
1441        .erase_sleep        = test_erase_sleep,
1442        .power_cycles       = i,
1443        .powerloss_behavior = POWERLOSS_BEHAVIOR,
1444        .powerloss_cb       = powerloss_longjmp,
1445        .powerloss_data     = &powerloss_jmp,
1446    };
1447
1448    int err = lfs_emubd_create(&cfg, &bdcfg);
1449    if (err) {
1450        fprintf(stderr, "error: could not create block device: %d\n", err);
1451        exit(-1);
1452    }
1453
1454    // run the test, increasing power-cycles as power-loss events occur
1455    printf("running ");
1456    perm_printid(suite, case_, NULL, 0);
1457    printf("\n");
1458
1459    while (true) {
1460        if (!setjmp(powerloss_jmp)) {
1461            // run the test
1462            case_->run(&cfg);
1463            break;
1464        }
1465
1466        // power-loss!
1467        printf("powerloss ");
1468        perm_printid(suite, case_, NULL, 0);
1469        printf(":");
1470        for (lfs_emubd_powercycles_t j = 1; j <= i; j++) {
1471            leb16_print(j);
1472        }
1473        printf("\n");
1474
1475        i += 1;
1476        lfs_emubd_setpowercycles(&cfg, i);
1477    }
1478
1479    printf("finished ");
1480    perm_printid(suite, case_, NULL, 0);
1481    printf("\n");
1482
1483    // cleanup
1484    err = lfs_emubd_destroy(&cfg);
1485    if (err) {
1486        fprintf(stderr, "error: could not destroy block device: %d\n", err);
1487        exit(-1);
1488    }
1489}
1490
1491static void run_powerloss_log(
1492        const lfs_emubd_powercycles_t *cycles,
1493        size_t cycle_count,
1494        const struct test_suite *suite,
1495        const struct test_case *case_) {
1496    (void)cycles;
1497    (void)cycle_count;
1498    (void)suite;
1499
1500    // create block device and configuration
1501    lfs_emubd_t bd;
1502    jmp_buf powerloss_jmp;
1503    volatile lfs_emubd_powercycles_t i = 1;
1504
1505    struct lfs_config cfg = {
1506        .context            = &bd,
1507        .read               = lfs_emubd_read,
1508        .prog               = lfs_emubd_prog,
1509        .erase              = lfs_emubd_erase,
1510        .sync               = lfs_emubd_sync,
1511        .read_size          = READ_SIZE,
1512        .prog_size          = PROG_SIZE,
1513        .block_size         = BLOCK_SIZE,
1514        .block_count        = BLOCK_COUNT,
1515        .block_cycles       = BLOCK_CYCLES,
1516        .cache_size         = CACHE_SIZE,
1517        .lookahead_size     = LOOKAHEAD_SIZE,
1518    #ifdef LFS_MULTIVERSION
1519        .disk_version       = DISK_VERSION,
1520    #endif
1521    };
1522
1523    struct lfs_emubd_config bdcfg = {
1524        .read_size          = READ_SIZE,
1525        .prog_size          = PROG_SIZE,
1526        .erase_size         = ERASE_SIZE,
1527        .erase_count        = ERASE_COUNT,
1528        .erase_value        = ERASE_VALUE,
1529        .erase_cycles       = ERASE_CYCLES,
1530        .badblock_behavior  = BADBLOCK_BEHAVIOR,
1531        .disk_path          = test_disk_path,
1532        .read_sleep         = test_read_sleep,
1533        .prog_sleep         = test_prog_sleep,
1534        .erase_sleep        = test_erase_sleep,
1535        .power_cycles       = i,
1536        .powerloss_behavior = POWERLOSS_BEHAVIOR,
1537        .powerloss_cb       = powerloss_longjmp,
1538        .powerloss_data     = &powerloss_jmp,
1539    };
1540
1541    int err = lfs_emubd_create(&cfg, &bdcfg);
1542    if (err) {
1543        fprintf(stderr, "error: could not create block device: %d\n", err);
1544        exit(-1);
1545    }
1546
1547    // run the test, increasing power-cycles as power-loss events occur
1548    printf("running ");
1549    perm_printid(suite, case_, NULL, 0);
1550    printf("\n");
1551
1552    while (true) {
1553        if (!setjmp(powerloss_jmp)) {
1554            // run the test
1555            case_->run(&cfg);
1556            break;
1557        }
1558
1559        // power-loss!
1560        printf("powerloss ");
1561        perm_printid(suite, case_, NULL, 0);
1562        printf(":");
1563        for (lfs_emubd_powercycles_t j = 1; j <= i; j *= 2) {
1564            leb16_print(j);
1565        }
1566        printf("\n");
1567
1568        i *= 2;
1569        lfs_emubd_setpowercycles(&cfg, i);
1570    }
1571
1572    printf("finished ");
1573    perm_printid(suite, case_, NULL, 0);
1574    printf("\n");
1575
1576    // cleanup
1577    err = lfs_emubd_destroy(&cfg);
1578    if (err) {
1579        fprintf(stderr, "error: could not destroy block device: %d\n", err);
1580        exit(-1);
1581    }
1582}
1583
1584static void run_powerloss_cycles(
1585        const lfs_emubd_powercycles_t *cycles,
1586        size_t cycle_count,
1587        const struct test_suite *suite,
1588        const struct test_case *case_) {
1589    (void)suite;
1590
1591    // create block device and configuration
1592    lfs_emubd_t bd;
1593    jmp_buf powerloss_jmp;
1594    volatile size_t i = 0;
1595
1596    struct lfs_config cfg = {
1597        .context            = &bd,
1598        .read               = lfs_emubd_read,
1599        .prog               = lfs_emubd_prog,
1600        .erase              = lfs_emubd_erase,
1601        .sync               = lfs_emubd_sync,
1602        .read_size          = READ_SIZE,
1603        .prog_size          = PROG_SIZE,
1604        .block_size         = BLOCK_SIZE,
1605        .block_count        = BLOCK_COUNT,
1606        .block_cycles       = BLOCK_CYCLES,
1607        .cache_size         = CACHE_SIZE,
1608        .lookahead_size     = LOOKAHEAD_SIZE,
1609    #ifdef LFS_MULTIVERSION
1610        .disk_version       = DISK_VERSION,
1611    #endif
1612    };
1613
1614    struct lfs_emubd_config bdcfg = {
1615        .read_size          = READ_SIZE,
1616        .prog_size          = PROG_SIZE,
1617        .erase_size         = ERASE_SIZE,
1618        .erase_count        = ERASE_COUNT,
1619        .erase_value        = ERASE_VALUE,
1620        .erase_cycles       = ERASE_CYCLES,
1621        .badblock_behavior  = BADBLOCK_BEHAVIOR,
1622        .disk_path          = test_disk_path,
1623        .read_sleep         = test_read_sleep,
1624        .prog_sleep         = test_prog_sleep,
1625        .erase_sleep        = test_erase_sleep,
1626        .power_cycles       = (i < cycle_count) ? cycles[i] : 0,
1627        .powerloss_behavior = POWERLOSS_BEHAVIOR,
1628        .powerloss_cb       = powerloss_longjmp,
1629        .powerloss_data     = &powerloss_jmp,
1630    };
1631
1632    int err = lfs_emubd_create(&cfg, &bdcfg);
1633    if (err) {
1634        fprintf(stderr, "error: could not create block device: %d\n", err);
1635        exit(-1);
1636    }
1637
1638    // run the test, increasing power-cycles as power-loss events occur
1639    printf("running ");
1640    perm_printid(suite, case_, NULL, 0);
1641    printf("\n");
1642
1643    while (true) {
1644        if (!setjmp(powerloss_jmp)) {
1645            // run the test
1646            case_->run(&cfg);
1647            break;
1648        }
1649
1650        // power-loss!
1651        assert(i <= cycle_count);
1652        printf("powerloss ");
1653        perm_printid(suite, case_, cycles, i+1);
1654        printf("\n");
1655
1656        i += 1;
1657        lfs_emubd_setpowercycles(&cfg,
1658                (i < cycle_count) ? cycles[i] : 0);
1659    }
1660
1661    printf("finished ");
1662    perm_printid(suite, case_, NULL, 0);
1663    printf("\n");
1664
1665    // cleanup
1666    err = lfs_emubd_destroy(&cfg);
1667    if (err) {
1668        fprintf(stderr, "error: could not destroy block device: %d\n", err);
1669        exit(-1);
1670    }
1671}
1672
1673struct powerloss_exhaustive_state {
1674    struct lfs_config *cfg;
1675
1676    lfs_emubd_t *branches;
1677    size_t branch_count;
1678    size_t branch_capacity;
1679};
1680
1681struct powerloss_exhaustive_cycles {
1682    lfs_emubd_powercycles_t *cycles;
1683    size_t cycle_count;
1684    size_t cycle_capacity;
1685};
1686
1687static void powerloss_exhaustive_branch(void *c) {
1688    struct powerloss_exhaustive_state *state = c;
1689    // append to branches
1690    lfs_emubd_t *branch = mappend(
1691            (void**)&state->branches,
1692            sizeof(lfs_emubd_t),
1693            &state->branch_count,
1694            &state->branch_capacity);
1695    if (!branch) {
1696        fprintf(stderr, "error: exhaustive: out of memory\n");
1697        exit(-1);
1698    }
1699
1700    // create copy-on-write copy
1701    int err = lfs_emubd_copy(state->cfg, branch);
1702    if (err) {
1703        fprintf(stderr, "error: exhaustive: could not create bd copy\n");
1704        exit(-1);
1705    }
1706
1707    // also trigger on next power cycle
1708    lfs_emubd_setpowercycles(state->cfg, 1);
1709}
1710
1711static void run_powerloss_exhaustive_layer(
1712        struct powerloss_exhaustive_cycles *cycles,
1713        const struct test_suite *suite,
1714        const struct test_case *case_,
1715        struct lfs_config *cfg,
1716        struct lfs_emubd_config *bdcfg,
1717        size_t depth) {
1718    (void)suite;
1719
1720    struct powerloss_exhaustive_state state = {
1721        .cfg = cfg,
1722        .branches = NULL,
1723        .branch_count = 0,
1724        .branch_capacity = 0,
1725    };
1726
1727    // run through the test without additional powerlosses, collecting possible
1728    // branches as we do so
1729    lfs_emubd_setpowercycles(state.cfg, depth > 0 ? 1 : 0);
1730    bdcfg->powerloss_data = &state;
1731
1732    // run the tests
1733    case_->run(cfg);
1734
1735    // aggressively clean up memory here to try to keep our memory usage low
1736    int err = lfs_emubd_destroy(cfg);
1737    if (err) {
1738        fprintf(stderr, "error: could not destroy block device: %d\n", err);
1739        exit(-1);
1740    }
1741
1742    // recurse into each branch
1743    for (size_t i = 0; i < state.branch_count; i++) {
1744        // first push and print the branch
1745        lfs_emubd_powercycles_t *cycle = mappend(
1746                (void**)&cycles->cycles,
1747                sizeof(lfs_emubd_powercycles_t),
1748                &cycles->cycle_count,
1749                &cycles->cycle_capacity);
1750        if (!cycle) {
1751            fprintf(stderr, "error: exhaustive: out of memory\n");
1752            exit(-1);
1753        }
1754        *cycle = i+1;
1755
1756        printf("powerloss ");
1757        perm_printid(suite, case_, cycles->cycles, cycles->cycle_count);
1758        printf("\n");
1759
1760        // now recurse
1761        cfg->context = &state.branches[i];
1762        run_powerloss_exhaustive_layer(cycles,
1763                suite, case_,
1764                cfg, bdcfg, depth-1);
1765
1766        // pop the cycle
1767        cycles->cycle_count -= 1;
1768    }
1769
1770    // clean up memory
1771    free(state.branches);
1772}
1773
1774static void run_powerloss_exhaustive(
1775        const lfs_emubd_powercycles_t *cycles,
1776        size_t cycle_count,
1777        const struct test_suite *suite,
1778        const struct test_case *case_) {
1779    (void)cycles;
1780    (void)suite;
1781
1782    // create block device and configuration
1783    lfs_emubd_t bd;
1784
1785    struct lfs_config cfg = {
1786        .context            = &bd,
1787        .read               = lfs_emubd_read,
1788        .prog               = lfs_emubd_prog,
1789        .erase              = lfs_emubd_erase,
1790        .sync               = lfs_emubd_sync,
1791        .read_size          = READ_SIZE,
1792        .prog_size          = PROG_SIZE,
1793        .block_size         = BLOCK_SIZE,
1794        .block_count        = BLOCK_COUNT,
1795        .block_cycles       = BLOCK_CYCLES,
1796        .cache_size         = CACHE_SIZE,
1797        .lookahead_size     = LOOKAHEAD_SIZE,
1798    #ifdef LFS_MULTIVERSION
1799        .disk_version       = DISK_VERSION,
1800    #endif
1801    };
1802
1803    struct lfs_emubd_config bdcfg = {
1804        .read_size          = READ_SIZE,
1805        .prog_size          = PROG_SIZE,
1806        .erase_size         = ERASE_SIZE,
1807        .erase_count        = ERASE_COUNT,
1808        .erase_value        = ERASE_VALUE,
1809        .erase_cycles       = ERASE_CYCLES,
1810        .badblock_behavior  = BADBLOCK_BEHAVIOR,
1811        .disk_path          = test_disk_path,
1812        .read_sleep         = test_read_sleep,
1813        .prog_sleep         = test_prog_sleep,
1814        .erase_sleep        = test_erase_sleep,
1815        .powerloss_behavior = POWERLOSS_BEHAVIOR,
1816        .powerloss_cb       = powerloss_exhaustive_branch,
1817        .powerloss_data     = NULL,
1818    };
1819
1820    int err = lfs_emubd_create(&cfg, &bdcfg);
1821    if (err) {
1822        fprintf(stderr, "error: could not create block device: %d\n", err);
1823        exit(-1);
1824    }
1825
1826    // run the test, increasing power-cycles as power-loss events occur
1827    printf("running ");
1828    perm_printid(suite, case_, NULL, 0);
1829    printf("\n");
1830
1831    // recursively exhaust each layer of powerlosses
1832    run_powerloss_exhaustive_layer(
1833            &(struct powerloss_exhaustive_cycles){NULL, 0, 0},
1834            suite, case_,
1835            &cfg, &bdcfg, cycle_count);
1836
1837    printf("finished ");
1838    perm_printid(suite, case_, NULL, 0);
1839    printf("\n");
1840}
1841
1842
1843const test_powerloss_t builtin_powerlosses[] = {
1844    {"none",       run_powerloss_none,       NULL, 0},
1845    {"log",        run_powerloss_log,        NULL, 0},
1846    {"linear",     run_powerloss_linear,     NULL, 0},
1847    {"exhaustive", run_powerloss_exhaustive, NULL, SIZE_MAX},
1848    {NULL, NULL, NULL, 0},
1849};
1850
1851const char *const builtin_powerlosses_help[] = {
1852    "Run with no power-losses.",
1853    "Run with exponentially-decreasing power-losses.",
1854    "Run with linearly-decreasing power-losses.",
1855    "Run a all permutations of power-losses, this may take a while.",
1856    "Run a all permutations of n power-losses.",
1857    "Run a custom comma-separated set of power-losses.",
1858    "Run a custom leb16-encoded set of power-losses.",
1859};
1860
1861// default to -Pnone,linear, which provides a good heuristic while still
1862// running quickly
1863const test_powerloss_t *test_powerlosses = (const test_powerloss_t[]){
1864    {"none",   run_powerloss_none,   NULL, 0},
1865    {"linear", run_powerloss_linear, NULL, 0},
1866};
1867size_t test_powerloss_count = 2;
1868
1869static void list_powerlosses(void) {
1870    // at least size so that names fit
1871    unsigned name_width = 23;
1872    for (size_t i = 0; builtin_powerlosses[i].name; i++) {
1873        size_t len = strlen(builtin_powerlosses[i].name);
1874        if (len > name_width) {
1875            name_width = len;
1876        }
1877    }
1878    name_width = 4*((name_width+1+4-1)/4)-1;
1879
1880    printf("%-*s %s\n", name_width, "scenario", "description");
1881    size_t i = 0;
1882    for (; builtin_powerlosses[i].name; i++) {
1883        printf("%-*s %s\n",
1884                name_width,
1885                builtin_powerlosses[i].name,
1886                builtin_powerlosses_help[i]);
1887    }
1888
1889    // a couple more options with special parsing
1890    printf("%-*s %s\n", name_width, "1,2,3",   builtin_powerlosses_help[i+0]);
1891    printf("%-*s %s\n", name_width, "{1,2,3}", builtin_powerlosses_help[i+1]);
1892    printf("%-*s %s\n", name_width, ":1248g1", builtin_powerlosses_help[i+2]);
1893}
1894
1895
1896// global test step count
1897size_t test_step = 0;
1898
1899void perm_run(
1900        void *data,
1901        const struct test_suite *suite,
1902        const struct test_case *case_,
1903        const test_powerloss_t *powerloss) {
1904    (void)data;
1905
1906    // skip this step?
1907    if (!(test_step >= test_step_start
1908            && test_step < test_step_stop
1909            && (test_step-test_step_start) % test_step_step == 0)) {
1910        test_step += 1;
1911        return;
1912    }
1913    test_step += 1;
1914
1915    // filter?
1916    if (case_->filter && !case_->filter()) {
1917        printf("skipped ");
1918        perm_printid(suite, case_, NULL, 0);
1919        printf("\n");
1920        return;
1921    }
1922
1923    powerloss->run(
1924            powerloss->cycles, powerloss->cycle_count,
1925            suite, case_);
1926}
1927
1928static void run(void) {
1929    // ignore disconnected pipes
1930    signal(SIGPIPE, SIG_IGN);
1931
1932    for (size_t t = 0; t < test_id_count; t++) {
1933        for (size_t i = 0; i < TEST_SUITE_COUNT; i++) {
1934            test_define_suite(&test_suites[i]);
1935
1936            for (size_t j = 0; j < test_suites[i].case_count; j++) {
1937                // does neither suite nor case name match?
1938                if (test_ids[t].name && !(
1939                        strcmp(test_ids[t].name,
1940                            test_suites[i].name) == 0
1941                        || strcmp(test_ids[t].name,
1942                            test_suites[i].cases[j].name) == 0)) {
1943                    continue;
1944                }
1945
1946                case_forperm(
1947                        &test_suites[i],
1948                        &test_suites[i].cases[j],
1949                        test_ids[t].defines,
1950                        test_ids[t].define_count,
1951                        test_ids[t].cycles,
1952                        test_ids[t].cycle_count,
1953                        perm_run,
1954                        NULL);
1955            }
1956        }
1957    }
1958}
1959
1960
1961
1962// option handling
1963enum opt_flags {
1964    OPT_HELP                     = 'h',
1965    OPT_SUMMARY                  = 'Y',
1966    OPT_LIST_SUITES              = 'l',
1967    OPT_LIST_CASES               = 'L',
1968    OPT_LIST_SUITE_PATHS         = 1,
1969    OPT_LIST_CASE_PATHS          = 2,
1970    OPT_LIST_DEFINES             = 3,
1971    OPT_LIST_PERMUTATION_DEFINES = 4,
1972    OPT_LIST_IMPLICIT_DEFINES    = 5,
1973    OPT_LIST_GEOMETRIES          = 6,
1974    OPT_LIST_POWERLOSSES         = 7,
1975    OPT_DEFINE                   = 'D',
1976    OPT_GEOMETRY                 = 'G',
1977    OPT_POWERLOSS                = 'P',
1978    OPT_STEP                     = 's',
1979    OPT_DISK                     = 'd',
1980    OPT_TRACE                    = 't',
1981    OPT_TRACE_BACKTRACE          = 8,
1982    OPT_TRACE_PERIOD             = 9,
1983    OPT_TRACE_FREQ               = 10,
1984    OPT_READ_SLEEP               = 11,
1985    OPT_PROG_SLEEP               = 12,
1986    OPT_ERASE_SLEEP              = 13,
1987};
1988
1989const char *short_opts = "hYlLD:G:P:s:d:t:";
1990
1991const struct option long_opts[] = {
1992    {"help",             no_argument,       NULL, OPT_HELP},
1993    {"summary",          no_argument,       NULL, OPT_SUMMARY},
1994    {"list-suites",      no_argument,       NULL, OPT_LIST_SUITES},
1995    {"list-cases",       no_argument,       NULL, OPT_LIST_CASES},
1996    {"list-suite-paths", no_argument,       NULL, OPT_LIST_SUITE_PATHS},
1997    {"list-case-paths",  no_argument,       NULL, OPT_LIST_CASE_PATHS},
1998    {"list-defines",     no_argument,       NULL, OPT_LIST_DEFINES},
1999    {"list-permutation-defines",
2000                         no_argument,       NULL, OPT_LIST_PERMUTATION_DEFINES},
2001    {"list-implicit-defines",
2002                         no_argument,       NULL, OPT_LIST_IMPLICIT_DEFINES},
2003    {"list-geometries",  no_argument,       NULL, OPT_LIST_GEOMETRIES},
2004    {"list-powerlosses", no_argument,       NULL, OPT_LIST_POWERLOSSES},
2005    {"define",           required_argument, NULL, OPT_DEFINE},
2006    {"geometry",         required_argument, NULL, OPT_GEOMETRY},
2007    {"powerloss",        required_argument, NULL, OPT_POWERLOSS},
2008    {"step",             required_argument, NULL, OPT_STEP},
2009    {"disk",             required_argument, NULL, OPT_DISK},
2010    {"trace",            required_argument, NULL, OPT_TRACE},
2011    {"trace-backtrace",  no_argument,       NULL, OPT_TRACE_BACKTRACE},
2012    {"trace-period",     required_argument, NULL, OPT_TRACE_PERIOD},
2013    {"trace-freq",       required_argument, NULL, OPT_TRACE_FREQ},
2014    {"read-sleep",       required_argument, NULL, OPT_READ_SLEEP},
2015    {"prog-sleep",       required_argument, NULL, OPT_PROG_SLEEP},
2016    {"erase-sleep",      required_argument, NULL, OPT_ERASE_SLEEP},
2017    {NULL, 0, NULL, 0},
2018};
2019
2020const char *const help_text[] = {
2021    "Show this help message.",
2022    "Show quick summary.",
2023    "List test suites.",
2024    "List test cases.",
2025    "List the path for each test suite.",
2026    "List the path and line number for each test case.",
2027    "List all defines in this test-runner.",
2028    "List explicit defines in this test-runner.",
2029    "List implicit defines in this test-runner.",
2030    "List the available disk geometries.",
2031    "List the available power-loss scenarios.",
2032    "Override a test define.",
2033    "Comma-separated list of disk geometries to test.",
2034    "Comma-separated list of power-loss scenarios to test.",
2035    "Comma-separated range of test permutations to run (start,stop,step).",
2036    "Direct block device operations to this file.",
2037    "Direct trace output to this file.",
2038    "Include a backtrace with every trace statement.",
2039    "Sample trace output at this period in cycles.",
2040    "Sample trace output at this frequency in hz.",
2041    "Artificial read delay in seconds.",
2042    "Artificial prog delay in seconds.",
2043    "Artificial erase delay in seconds.",
2044};
2045
2046int main(int argc, char **argv) {
2047    void (*op)(void) = run;
2048
2049    size_t test_override_capacity = 0;
2050    size_t test_geometry_capacity = 0;
2051    size_t test_powerloss_capacity = 0;
2052    size_t test_id_capacity = 0;
2053
2054    // parse options
2055    while (true) {
2056        int c = getopt_long(argc, argv, short_opts, long_opts, NULL);
2057        switch (c) {
2058            // generate help message
2059            case OPT_HELP: {
2060                printf("usage: %s [options] [test_id]\n", argv[0]);
2061                printf("\n");
2062
2063                printf("options:\n");
2064                size_t i = 0;
2065                while (long_opts[i].name) {
2066                    size_t indent;
2067                    if (long_opts[i].has_arg == no_argument) {
2068                        if (long_opts[i].val >= '0' && long_opts[i].val < 'z') {
2069                            indent = printf("  -%c, --%s ",
2070                                    long_opts[i].val,
2071                                    long_opts[i].name);
2072                        } else {
2073                            indent = printf("  --%s ",
2074                                    long_opts[i].name);
2075                        }
2076                    } else {
2077                        if (long_opts[i].val >= '0' && long_opts[i].val < 'z') {
2078                            indent = printf("  -%c %s, --%s %s ",
2079                                    long_opts[i].val,
2080                                    long_opts[i].name,
2081                                    long_opts[i].name,
2082                                    long_opts[i].name);
2083                        } else {
2084                            indent = printf("  --%s %s ",
2085                                    long_opts[i].name,
2086                                    long_opts[i].name);
2087                        }
2088                    }
2089
2090                    // a quick, hacky, byte-level method for text wrapping
2091                    size_t len = strlen(help_text[i]);
2092                    size_t j = 0;
2093                    if (indent < 24) {
2094                        printf("%*s %.80s\n",
2095                                (int)(24-1-indent),
2096                                "",
2097                                &help_text[i][j]);
2098                        j += 80;
2099                    } else {
2100                        printf("\n");
2101                    }
2102
2103                    while (j < len) {
2104                        printf("%24s%.80s\n", "", &help_text[i][j]);
2105                        j += 80;
2106                    }
2107
2108                    i += 1;
2109                }
2110
2111                printf("\n");
2112                exit(0);
2113            }
2114            // summary/list flags
2115            case OPT_SUMMARY:
2116                op = summary;
2117                break;
2118            case OPT_LIST_SUITES:
2119                op = list_suites;
2120                break;
2121            case OPT_LIST_CASES:
2122                op = list_cases;
2123                break;
2124            case OPT_LIST_SUITE_PATHS:
2125                op = list_suite_paths;
2126                break;
2127            case OPT_LIST_CASE_PATHS:
2128                op = list_case_paths;
2129                break;
2130            case OPT_LIST_DEFINES:
2131                op = list_defines;
2132                break;
2133            case OPT_LIST_PERMUTATION_DEFINES:
2134                op = list_permutation_defines;
2135                break;
2136            case OPT_LIST_IMPLICIT_DEFINES:
2137                op = list_implicit_defines;
2138                break;
2139            case OPT_LIST_GEOMETRIES:
2140                op = list_geometries;
2141                break;
2142            case OPT_LIST_POWERLOSSES:
2143                op = list_powerlosses;
2144                break;
2145            // configuration
2146            case OPT_DEFINE: {
2147                // allocate space
2148                test_override_t *override = mappend(
2149                        (void**)&test_overrides,
2150                        sizeof(test_override_t),
2151                        &test_override_count,
2152                        &test_override_capacity);
2153
2154                // parse into string key/intmax_t value, cannibalizing the
2155                // arg in the process
2156                char *sep = strchr(optarg, '=');
2157                char *parsed = NULL;
2158                if (!sep) {
2159                    goto invalid_define;
2160                }
2161                *sep = '\0';
2162                override->name = optarg;
2163                optarg = sep+1;
2164
2165                // parse comma-separated permutations
2166                {
2167                    override->defines = NULL;
2168                    override->permutations = 0;
2169                    size_t override_capacity = 0;
2170                    while (true) {
2171                        optarg += strspn(optarg, " ");
2172
2173                        if (strncmp(optarg, "range", strlen("range")) == 0) {
2174                            // range of values
2175                            optarg += strlen("range");
2176                            optarg += strspn(optarg, " ");
2177                            if (*optarg != '(') {
2178                                goto invalid_define;
2179                            }
2180                            optarg += 1;
2181
2182                            intmax_t start = strtoumax(optarg, &parsed, 0);
2183                            intmax_t stop = -1;
2184                            intmax_t step = 1;
2185                            // allow empty string for start=0
2186                            if (parsed == optarg) {
2187                                start = 0;
2188                            }
2189                            optarg = parsed + strspn(parsed, " ");
2190
2191                            if (*optarg != ',' && *optarg != ')') {
2192                                goto invalid_define;
2193                            }
2194
2195                            if (*optarg == ',') {
2196                                optarg += 1;
2197                                stop = strtoumax(optarg, &parsed, 0);
2198                                // allow empty string for stop=end
2199                                if (parsed == optarg) {
2200                                    stop = -1;
2201                                }
2202                                optarg = parsed + strspn(parsed, " ");
2203
2204                                if (*optarg != ',' && *optarg != ')') {
2205                                    goto invalid_define;
2206                                }
2207
2208                                if (*optarg == ',') {
2209                                    optarg += 1;
2210                                    step = strtoumax(optarg, &parsed, 0);
2211                                    // allow empty string for stop=1
2212                                    if (parsed == optarg) {
2213                                        step = 1;
2214                                    }
2215                                    optarg = parsed + strspn(parsed, " ");
2216
2217                                    if (*optarg != ')') {
2218                                        goto invalid_define;
2219                                    }
2220                                }
2221                            } else {
2222                                // single value = stop only
2223                                stop = start;
2224                                start = 0;
2225                            }
2226
2227                            if (*optarg != ')') {
2228                                goto invalid_define;
2229                            }
2230                            optarg += 1;
2231
2232                            // calculate the range of values
2233                            assert(step != 0);
2234                            for (intmax_t i = start;
2235                                    (step < 0)
2236                                        ? i > stop
2237                                        : (uintmax_t)i < (uintmax_t)stop;
2238                                    i += step) {
2239                                *(intmax_t*)mappend(
2240                                        (void**)&override->defines,
2241                                        sizeof(intmax_t),
2242                                        &override->permutations,
2243                                        &override_capacity) = i;
2244                            }
2245                        } else if (*optarg != '\0') {
2246                            // single value
2247                            intmax_t define = strtoimax(optarg, &parsed, 0);
2248                            if (parsed == optarg) {
2249                                goto invalid_define;
2250                            }
2251                            optarg = parsed + strspn(parsed, " ");
2252                            *(intmax_t*)mappend(
2253                                    (void**)&override->defines,
2254                                    sizeof(intmax_t),
2255                                    &override->permutations,
2256                                    &override_capacity) = define;
2257                        } else {
2258                            break;
2259                        }
2260
2261                        if (*optarg == ',') {
2262                            optarg += 1;
2263                        }
2264                    }
2265                }
2266                assert(override->permutations > 0);
2267                break;
2268
2269invalid_define:
2270                fprintf(stderr, "error: invalid define: %s\n", optarg);
2271                exit(-1);
2272            }
2273            case OPT_GEOMETRY: {
2274                // reset our geometry scenarios
2275                if (test_geometry_capacity > 0) {
2276                    free((test_geometry_t*)test_geometries);
2277                }
2278                test_geometries = NULL;
2279                test_geometry_count = 0;
2280                test_geometry_capacity = 0;
2281
2282                // parse the comma separated list of disk geometries
2283                while (*optarg) {
2284                    // allocate space
2285                    test_geometry_t *geometry = mappend(
2286                            (void**)&test_geometries,
2287                            sizeof(test_geometry_t),
2288                            &test_geometry_count,
2289                            &test_geometry_capacity);
2290
2291                    // parse the disk geometry
2292                    optarg += strspn(optarg, " ");
2293
2294                    // named disk geometry
2295                    size_t len = strcspn(optarg, " ,");
2296                    for (size_t i = 0; builtin_geometries[i].name; i++) {
2297                        if (len == strlen(builtin_geometries[i].name)
2298                                && memcmp(optarg,
2299                                    builtin_geometries[i].name,
2300                                    len) == 0)  {
2301                            *geometry = builtin_geometries[i];
2302                            optarg += len;
2303                            goto geometry_next;
2304                        }
2305                    }
2306
2307                    // comma-separated read/prog/erase/count
2308                    if (*optarg == '{') {
2309                        lfs_size_t sizes[4];
2310                        size_t count = 0;
2311
2312                        char *s = optarg + 1;
2313                        while (count < 4) {
2314                            char *parsed = NULL;
2315                            sizes[count] = strtoumax(s, &parsed, 0);
2316                            count += 1;
2317
2318                            s = parsed + strspn(parsed, " ");
2319                            if (*s == ',') {
2320                                s += 1;
2321                                continue;
2322                            } else if (*s == '}') {
2323                                s += 1;
2324                                break;
2325                            } else {
2326                                goto geometry_unknown;
2327                            }
2328                        }
2329
2330                        // allow implicit r=p and p=e for common geometries
2331                        memset(geometry, 0, sizeof(test_geometry_t));
2332                        if (count >= 3) {
2333                            geometry->defines[READ_SIZE_i]
2334                                    = TEST_LIT(sizes[0]);
2335                            geometry->defines[PROG_SIZE_i]
2336                                    = TEST_LIT(sizes[1]);
2337                            geometry->defines[ERASE_SIZE_i]
2338                                    = TEST_LIT(sizes[2]);
2339                        } else if (count >= 2) {
2340                            geometry->defines[PROG_SIZE_i]
2341                                    = TEST_LIT(sizes[0]);
2342                            geometry->defines[ERASE_SIZE_i]
2343                                    = TEST_LIT(sizes[1]);
2344                        } else {
2345                            geometry->defines[ERASE_SIZE_i]
2346                                    = TEST_LIT(sizes[0]);
2347                        }
2348                        if (count >= 4) {
2349                            geometry->defines[ERASE_COUNT_i]
2350                                    = TEST_LIT(sizes[3]);
2351                        }
2352                        optarg = s;
2353                        goto geometry_next;
2354                    }
2355
2356                    // leb16-encoded read/prog/erase/count
2357                    if (*optarg == ':') {
2358                        lfs_size_t sizes[4];
2359                        size_t count = 0;
2360
2361                        char *s = optarg + 1;
2362                        while (true) {
2363                            char *parsed = NULL;
2364                            uintmax_t x = leb16_parse(s, &parsed);
2365                            if (parsed == s || count >= 4) {
2366                                break;
2367                            }
2368
2369                            sizes[count] = x;
2370                            count += 1;
2371                            s = parsed;
2372                        }
2373
2374                        // allow implicit r=p and p=e for common geometries
2375                        memset(geometry, 0, sizeof(test_geometry_t));
2376                        if (count >= 3) {
2377                            geometry->defines[READ_SIZE_i]
2378                                    = TEST_LIT(sizes[0]);
2379                            geometry->defines[PROG_SIZE_i]
2380                                    = TEST_LIT(sizes[1]);
2381                            geometry->defines[ERASE_SIZE_i]
2382                                    = TEST_LIT(sizes[2]);
2383                        } else if (count >= 2) {
2384                            geometry->defines[PROG_SIZE_i]
2385                                    = TEST_LIT(sizes[0]);
2386                            geometry->defines[ERASE_SIZE_i]
2387                                    = TEST_LIT(sizes[1]);
2388                        } else {
2389                            geometry->defines[ERASE_SIZE_i]
2390                                    = TEST_LIT(sizes[0]);
2391                        }
2392                        if (count >= 4) {
2393                            geometry->defines[ERASE_COUNT_i]
2394                                    = TEST_LIT(sizes[3]);
2395                        }
2396                        optarg = s;
2397                        goto geometry_next;
2398                    }
2399
2400geometry_unknown:
2401                    // unknown scenario?
2402                    fprintf(stderr, "error: unknown disk geometry: %s\n",
2403                            optarg);
2404                    exit(-1);
2405
2406geometry_next:
2407                    optarg += strspn(optarg, " ");
2408                    if (*optarg == ',') {
2409                        optarg += 1;
2410                    } else if (*optarg == '\0') {
2411                        break;
2412                    } else {
2413                        goto geometry_unknown;
2414                    }
2415                }
2416                break;
2417            }
2418            case OPT_POWERLOSS: {
2419                // reset our powerloss scenarios
2420                if (test_powerloss_capacity > 0) {
2421                    free((test_powerloss_t*)test_powerlosses);
2422                }
2423                test_powerlosses = NULL;
2424                test_powerloss_count = 0;
2425                test_powerloss_capacity = 0;
2426
2427                // parse the comma separated list of power-loss scenarios
2428                while (*optarg) {
2429                    // allocate space
2430                    test_powerloss_t *powerloss = mappend(
2431                            (void**)&test_powerlosses,
2432                            sizeof(test_powerloss_t),
2433                            &test_powerloss_count,
2434                            &test_powerloss_capacity);
2435
2436                    // parse the power-loss scenario
2437                    optarg += strspn(optarg, " ");
2438
2439                    // named power-loss scenario
2440                    size_t len = strcspn(optarg, " ,");
2441                    for (size_t i = 0; builtin_powerlosses[i].name; i++) {
2442                        if (len == strlen(builtin_powerlosses[i].name)
2443                                && memcmp(optarg,
2444                                    builtin_powerlosses[i].name,
2445                                    len) == 0) {
2446                            *powerloss = builtin_powerlosses[i];
2447                            optarg += len;
2448                            goto powerloss_next;
2449                        }
2450                    }
2451
2452                    // comma-separated permutation
2453                    if (*optarg == '{') {
2454                        lfs_emubd_powercycles_t *cycles = NULL;
2455                        size_t cycle_count = 0;
2456                        size_t cycle_capacity = 0;
2457
2458                        char *s = optarg + 1;
2459                        while (true) {
2460                            char *parsed = NULL;
2461                            *(lfs_emubd_powercycles_t*)mappend(
2462                                    (void**)&cycles,
2463                                    sizeof(lfs_emubd_powercycles_t),
2464                                    &cycle_count,
2465                                    &cycle_capacity)
2466                                    = strtoumax(s, &parsed, 0);
2467
2468                            s = parsed + strspn(parsed, " ");
2469                            if (*s == ',') {
2470                                s += 1;
2471                                continue;
2472                            } else if (*s == '}') {
2473                                s += 1;
2474                                break;
2475                            } else {
2476                                goto powerloss_unknown;
2477                            }
2478                        }
2479
2480                        *powerloss = (test_powerloss_t){
2481                            .run = run_powerloss_cycles,
2482                            .cycles = cycles,
2483                            .cycle_count = cycle_count,
2484                        };
2485                        optarg = s;
2486                        goto powerloss_next;
2487                    }
2488
2489                    // leb16-encoded permutation
2490                    if (*optarg == ':') {
2491                        lfs_emubd_powercycles_t *cycles = NULL;
2492                        size_t cycle_count = 0;
2493                        size_t cycle_capacity = 0;
2494
2495                        char *s = optarg + 1;
2496                        while (true) {
2497                            char *parsed = NULL;
2498                            uintmax_t x = leb16_parse(s, &parsed);
2499                            if (parsed == s) {
2500                                break;
2501                            }
2502
2503                            *(lfs_emubd_powercycles_t*)mappend(
2504                                    (void**)&cycles,
2505                                    sizeof(lfs_emubd_powercycles_t),
2506                                    &cycle_count,
2507                                    &cycle_capacity) = x;
2508                            s = parsed;
2509                        }
2510
2511                        *powerloss = (test_powerloss_t){
2512                            .run = run_powerloss_cycles,
2513                            .cycles = cycles,
2514                            .cycle_count = cycle_count,
2515                        };
2516                        optarg = s;
2517                        goto powerloss_next;
2518                    }
2519
2520                    // exhaustive permutations
2521                    {
2522                        char *parsed = NULL;
2523                        size_t count = strtoumax(optarg, &parsed, 0);
2524                        if (parsed == optarg) {
2525                            goto powerloss_unknown;
2526                        }
2527                        *powerloss = (test_powerloss_t){
2528                            .run = run_powerloss_exhaustive,
2529                            .cycles = NULL,
2530                            .cycle_count = count,
2531                        };
2532                        optarg = (char*)parsed;
2533                        goto powerloss_next;
2534                    }
2535
2536powerloss_unknown:
2537                    // unknown scenario?
2538                    fprintf(stderr, "error: unknown power-loss scenario: %s\n",
2539                            optarg);
2540                    exit(-1);
2541
2542powerloss_next:
2543                    optarg += strspn(optarg, " ");
2544                    if (*optarg == ',') {
2545                        optarg += 1;
2546                    } else if (*optarg == '\0') {
2547                        break;
2548                    } else {
2549                        goto powerloss_unknown;
2550                    }
2551                }
2552                break;
2553            }
2554            case OPT_STEP: {
2555                char *parsed = NULL;
2556                test_step_start = strtoumax(optarg, &parsed, 0);
2557                test_step_stop = -1;
2558                test_step_step = 1;
2559                // allow empty string for start=0
2560                if (parsed == optarg) {
2561                    test_step_start = 0;
2562                }
2563                optarg = parsed + strspn(parsed, " ");
2564
2565                if (*optarg != ',' && *optarg != '\0') {
2566                    goto step_unknown;
2567                }
2568
2569                if (*optarg == ',') {
2570                    optarg += 1;
2571                    test_step_stop = strtoumax(optarg, &parsed, 0);
2572                    // allow empty string for stop=end
2573                    if (parsed == optarg) {
2574                        test_step_stop = -1;
2575                    }
2576                    optarg = parsed + strspn(parsed, " ");
2577
2578                    if (*optarg != ',' && *optarg != '\0') {
2579                        goto step_unknown;
2580                    }
2581
2582                    if (*optarg == ',') {
2583                        optarg += 1;
2584                        test_step_step = strtoumax(optarg, &parsed, 0);
2585                        // allow empty string for stop=1
2586                        if (parsed == optarg) {
2587                            test_step_step = 1;
2588                        }
2589                        optarg = parsed + strspn(parsed, " ");
2590
2591                        if (*optarg != '\0') {
2592                            goto step_unknown;
2593                        }
2594                    }
2595                } else {
2596                    // single value = stop only
2597                    test_step_stop = test_step_start;
2598                    test_step_start = 0;
2599                }
2600
2601                break;
2602step_unknown:
2603                fprintf(stderr, "error: invalid step: %s\n", optarg);
2604                exit(-1);
2605            }
2606            case OPT_DISK:
2607                test_disk_path = optarg;
2608                break;
2609            case OPT_TRACE:
2610                test_trace_path = optarg;
2611                break;
2612            case OPT_TRACE_BACKTRACE:
2613                test_trace_backtrace = true;
2614                break;
2615            case OPT_TRACE_PERIOD: {
2616                char *parsed = NULL;
2617                test_trace_period = strtoumax(optarg, &parsed, 0);
2618                if (parsed == optarg) {
2619                    fprintf(stderr, "error: invalid trace-period: %s\n", optarg);
2620                    exit(-1);
2621                }
2622                break;
2623            }
2624            case OPT_TRACE_FREQ: {
2625                char *parsed = NULL;
2626                test_trace_freq = strtoumax(optarg, &parsed, 0);
2627                if (parsed == optarg) {
2628                    fprintf(stderr, "error: invalid trace-freq: %s\n", optarg);
2629                    exit(-1);
2630                }
2631                break;
2632            }
2633            case OPT_READ_SLEEP: {
2634                char *parsed = NULL;
2635                double read_sleep = strtod(optarg, &parsed);
2636                if (parsed == optarg) {
2637                    fprintf(stderr, "error: invalid read-sleep: %s\n", optarg);
2638                    exit(-1);
2639                }
2640                test_read_sleep = read_sleep*1.0e9;
2641                break;
2642            }
2643            case OPT_PROG_SLEEP: {
2644                char *parsed = NULL;
2645                double prog_sleep = strtod(optarg, &parsed);
2646                if (parsed == optarg) {
2647                    fprintf(stderr, "error: invalid prog-sleep: %s\n", optarg);
2648                    exit(-1);
2649                }
2650                test_prog_sleep = prog_sleep*1.0e9;
2651                break;
2652            }
2653            case OPT_ERASE_SLEEP: {
2654                char *parsed = NULL;
2655                double erase_sleep = strtod(optarg, &parsed);
2656                if (parsed == optarg) {
2657                    fprintf(stderr, "error: invalid erase-sleep: %s\n", optarg);
2658                    exit(-1);
2659                }
2660                test_erase_sleep = erase_sleep*1.0e9;
2661                break;
2662            }
2663            // done parsing
2664            case -1:
2665                goto getopt_done;
2666            // unknown arg, getopt prints a message for us
2667            default:
2668                exit(-1);
2669        }
2670    }
2671getopt_done: ;
2672
2673    if (argc > optind) {
2674        // reset our test identifier list
2675        test_ids = NULL;
2676        test_id_count = 0;
2677        test_id_capacity = 0;
2678    }
2679
2680    // parse test identifier, if any, cannibalizing the arg in the process
2681    for (; argc > optind; optind++) {
2682        test_define_t *defines = NULL;
2683        size_t define_count = 0;
2684        lfs_emubd_powercycles_t *cycles = NULL;
2685        size_t cycle_count = 0;
2686
2687        // parse name, can be suite or case
2688        char *name = argv[optind];
2689        char *defines_ = strchr(name, ':');
2690        if (defines_) {
2691            *defines_ = '\0';
2692            defines_ += 1;
2693        }
2694
2695        // remove optional path and .toml suffix
2696        char *slash = strrchr(name, '/');
2697        if (slash) {
2698            name = slash+1;
2699        }
2700
2701        size_t name_len = strlen(name);
2702        if (name_len > 5 && strcmp(&name[name_len-5], ".toml") == 0) {
2703            name[name_len-5] = '\0';
2704        }
2705
2706        if (defines_) {
2707            // parse defines
2708            char *cycles_ = strchr(defines_, ':');
2709            if (cycles_) {
2710                *cycles_ = '\0';
2711                cycles_ += 1;
2712            }
2713
2714            while (true) {
2715                char *parsed;
2716                size_t d = leb16_parse(defines_, &parsed);
2717                intmax_t v = leb16_parse(parsed, &parsed);
2718                if (parsed == defines_) {
2719                    break;
2720                }
2721                defines_ = parsed;
2722
2723                if (d >= define_count) {
2724                    // align to power of two to avoid any superlinear growth
2725                    size_t ncount = 1 << lfs_npw2(d+1);
2726                    defines = realloc(defines,
2727                            ncount*sizeof(test_define_t));
2728                    memset(defines+define_count, 0,
2729                            (ncount-define_count)*sizeof(test_define_t));
2730                    define_count = ncount;
2731                }
2732                defines[d] = TEST_LIT(v);
2733            }
2734
2735            if (cycles_) {
2736                // parse power cycles
2737                size_t cycle_capacity = 0;
2738                while (*cycles_ != '\0') {
2739                    char *parsed = NULL;
2740                    *(lfs_emubd_powercycles_t*)mappend(
2741                            (void**)&cycles,
2742                            sizeof(lfs_emubd_powercycles_t),
2743                            &cycle_count,
2744                            &cycle_capacity)
2745                            = leb16_parse(cycles_, &parsed);
2746                    if (parsed == cycles_) {
2747                        fprintf(stderr, "error: "
2748                                "could not parse test cycles: %s\n",
2749                                cycles_);
2750                        exit(-1);
2751                    }
2752                    cycles_ = parsed;
2753                }
2754            }
2755        }
2756
2757        // append to identifier list
2758        *(test_id_t*)mappend(
2759                (void**)&test_ids,
2760                sizeof(test_id_t),
2761                &test_id_count,
2762                &test_id_capacity) = (test_id_t){
2763            .name = name,
2764            .defines = defines,
2765            .define_count = define_count,
2766            .cycles = cycles,
2767            .cycle_count = cycle_count,
2768        };
2769    }
2770
2771    // do the thing
2772    op();
2773
2774    // cleanup (need to be done for valgrind testing)
2775    test_define_cleanup();
2776    if (test_overrides) {
2777        for (size_t i = 0; i < test_override_count; i++) {
2778            free((void*)test_overrides[i].defines);
2779        }
2780        free((void*)test_overrides);
2781    }
2782    if (test_geometry_capacity) {
2783        free((void*)test_geometries);
2784    }
2785    if (test_powerloss_capacity) {
2786        for (size_t i = 0; i < test_powerloss_count; i++) {
2787            free((void*)test_powerlosses[i].cycles);
2788        }
2789        free((void*)test_powerlosses);
2790    }
2791    if (test_id_capacity) {
2792        for (size_t i = 0; i < test_id_count; i++) {
2793            free((void*)test_ids[i].defines);
2794            free((void*)test_ids[i].cycles);
2795        }
2796        free((void*)test_ids);
2797    }
2798}
2799