From 61455b6191bb5da9d8b799f4b7056570d3df8820 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 19 Aug 2022 18:57:55 -0500 Subject: [PATCH] Added back heuristic-based power-loss testing The main change here from the previous test framework design is: 1. Powerloss testing remains in-process, speeding up testing. 2. The state of a test, included all powerlosses, is encoded in the test id + leb16 encoded powerloss string. This means exhaustive testing can be run in CI, but then easily reproduced locally with full debugger support. For example: ./scripts/test.py test_dirs#reentrant_many_dir#10#1248g1g2 --gdb Will run the test test_dir, case reentrant_many_dir, permutation #10, with powerlosses at 1, 2, 4, 8, 16, and 32 cycles. Dropping into gdb if an assert fails. The changes to the block-device are a work-in-progress for a lazily-allocated/copy-on-write block device that I'm hoping will keep exhaustive testing relatively low-cost. --- Makefile | 2 +- bd/lfs_testbd.c | 448 +++++++++------ bd/lfs_testbd.h | 119 +++- lfs.h | 2 - lfs_util.h | 1 + runners/test_runner.c | 1257 +++++++++++++++++++++++++++++++---------- runners/test_runner.h | 27 +- scripts/test.py | 106 ++-- 8 files changed, 1391 insertions(+), 571 deletions(-) diff --git a/Makefile b/Makefile index bd829dd..416e760 100644 --- a/Makefile +++ b/Makefile @@ -110,10 +110,10 @@ tags: .PHONY: test-runner test-runner: override CFLAGS+=--coverage test-runner: $(BUILDDIR)runners/test_runner + rm -f $(TEST_GCDA) .PHONY: test test: test-runner - rm -f $(TEST_GCDA) ./scripts/test.py --runner=$(BUILDDIR)runners/test_runner $(TESTFLAGS) .PHONY: test-list diff --git a/bd/lfs_testbd.c b/bd/lfs_testbd.c index e9a96a9..4100ea8 100644 --- a/bd/lfs_testbd.c +++ b/bd/lfs_testbd.c @@ -11,6 +11,79 @@ #include +// access to lazily-allocated/copy-on-write blocks +// +// Note we can only modify a block if we have exclusive access to it (rc == 1) +// + +// TODO +__attribute__((unused)) +static void lfs_testbd_incblock(lfs_testbd_t *bd, lfs_block_t block) { + if (bd->blocks[block]) { + bd->blocks[block]->rc += 1; + } +} + +static void lfs_testbd_decblock(lfs_testbd_t *bd, lfs_block_t block) { + if (bd->blocks[block]) { + bd->blocks[block]->rc -= 1; + if (bd->blocks[block]->rc == 0) { + free(bd->blocks[block]); + bd->blocks[block] = NULL; + } + } +} + +static const lfs_testbd_block_t *lfs_testbd_getblock(lfs_testbd_t *bd, + lfs_block_t block) { + return bd->blocks[block]; +} + +static lfs_testbd_block_t *lfs_testbd_mutblock(lfs_testbd_t *bd, + lfs_block_t block, lfs_size_t block_size) { + if (bd->blocks[block] && bd->blocks[block]->rc == 1) { + // rc == 1? can modify + return bd->blocks[block]; + + } else if (bd->blocks[block]) { + // rc > 1? need to create a copy + lfs_testbd_block_t *b = malloc( + sizeof(lfs_testbd_block_t) + block_size); + if (!b) { + return NULL; + } + + memcpy(b, bd->blocks[block], sizeof(lfs_testbd_block_t) + block_size); + b->rc = 1; + + lfs_testbd_decblock(bd, block); + bd->blocks[block] = b; + return b; + + } else { + // no block? need to allocate + lfs_testbd_block_t *b = malloc( + sizeof(lfs_testbd_block_t) + block_size); + if (!b) { + return NULL; + } + + b->rc = 1; + b->wear = 0; + + // zero for consistency + memset(b->data, + (bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0, + block_size); + + bd->blocks[block] = b; + return b; + } +} + + +// testbd create/destroy + int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, const struct lfs_testbd_config *bdcfg) { LFS_TESTBD_TRACE("lfs_testbd_createcfg(%p {.context=%p, " @@ -20,62 +93,35 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, "\"%s\", " "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", " ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", " - ".buffer=%p, .wear_buffer=%p})", + ".powerloss_behavior=%"PRIu8", .powerloss_cb=%p, " + ".powerloss_data=%p, .track_branches=%d})", (void*)cfg, cfg->context, (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles, bdcfg->badblock_behavior, bdcfg->power_cycles, - bdcfg->buffer, bdcfg->wear_buffer); + bdcfg->powerloss_behavior, (void*)(uintptr_t)bdcfg->powerloss_cb, + bdcfg->powerloss_data, bdcfg->track_branches); lfs_testbd_t *bd = cfg->context; bd->cfg = bdcfg; + // allocate our block array, all blocks start as uninitialized + bd->blocks = malloc(cfg->block_count * sizeof(lfs_testbd_block_t*)); + if (!bd->blocks) { + LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + memset(bd->blocks, 0, cfg->block_count * sizeof(lfs_testbd_block_t*)); + // setup testing things - bd->persist = path; bd->power_cycles = bd->cfg->power_cycles; + bd->branches = NULL; + bd->branch_capacity = 0; + bd->branch_count = 0; - // create scratch block if we need it (for emulating erase values) - if (bd->cfg->erase_value != -1) { - if (bd->cfg->scratch_buffer) { - bd->scratch = bd->cfg->scratch_buffer; - } else { - bd->scratch = lfs_malloc(cfg->block_size); - if (!bd->scratch) { - LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM); - return LFS_ERR_NOMEM; - } - } - } - - // create map of wear - if (bd->cfg->erase_cycles) { - if (bd->cfg->wear_buffer) { - bd->wear = bd->cfg->wear_buffer; - } else { - bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t)*cfg->block_count); - if (!bd->wear) { - LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM); - return LFS_ERR_NOMEM; - } - } - - memset(bd->wear, 0, sizeof(lfs_testbd_wear_t) * cfg->block_count); - } - - // create underlying block device - if (bd->persist) { - int err = lfs_filebd_create(cfg, path); - LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err); - return err; - } else { - bd->u.ram.cfg = (struct lfs_rambd_config){ - .buffer = bd->cfg->buffer, - }; - int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg); - LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err); - return err; - } + LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", 0); + return 0; } int lfs_testbd_create(const struct lfs_config *cfg, const char *path) { @@ -99,65 +145,65 @@ int lfs_testbd_destroy(const struct lfs_config *cfg) { LFS_TESTBD_TRACE("lfs_testbd_destroy(%p)", (void*)cfg); lfs_testbd_t *bd = cfg->context; - if (bd->cfg->erase_value != -1 && !bd->cfg->scratch_buffer) { - lfs_free(bd->scratch); - } - if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) { - lfs_free(bd->wear); + // decrement reference counts + for (lfs_block_t i = 0; i < cfg->block_count; i++) { + lfs_testbd_decblock(bd, i); } - if (bd->persist) { - int err = lfs_filebd_destroy(cfg); - LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err); - return err; - } else { - int err = lfs_rambd_destroy(cfg); - LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err); - return err; - } + // free memory + free(bd->blocks); + free(bd->branches); + + LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", 0); + return 0; } -/// Internal mapping to block devices /// -static int lfs_testbd_rawread(const struct lfs_config *cfg, lfs_block_t block, - lfs_off_t off, void *buffer, lfs_size_t size) { - lfs_testbd_t *bd = cfg->context; - if (bd->persist) { - return lfs_filebd_read(cfg, block, off, buffer, size); - } else { - return lfs_rambd_read(cfg, block, off, buffer, size); - } -} -static int lfs_testbd_rawprog(const struct lfs_config *cfg, lfs_block_t block, - lfs_off_t off, const void *buffer, lfs_size_t size) { - lfs_testbd_t *bd = cfg->context; - if (bd->persist) { - return lfs_filebd_prog(cfg, block, off, buffer, size); - } else { - return lfs_rambd_prog(cfg, block, off, buffer, size); - } -} -static int lfs_testbd_rawerase(const struct lfs_config *cfg, - lfs_block_t block) { - lfs_testbd_t *bd = cfg->context; - if (bd->persist) { - return lfs_filebd_erase(cfg, block); - } else { - return lfs_rambd_erase(cfg, block); - } -} +///// Internal mapping to block devices /// +//static int lfs_testbd_rawread(const struct lfs_config *cfg, lfs_block_t block, +// lfs_off_t off, void *buffer, lfs_size_t size) { +// lfs_testbd_t *bd = cfg->context; +// if (bd->persist) { +// return lfs_filebd_read(cfg, block, off, buffer, size); +// } else { +// return lfs_rambd_read(cfg, block, off, buffer, size); +// } +//} +// +//static int lfs_testbd_rawprog(const struct lfs_config *cfg, lfs_block_t block, +// lfs_off_t off, const void *buffer, lfs_size_t size) { +// lfs_testbd_t *bd = cfg->context; +// if (bd->persist) { +// return lfs_filebd_prog(cfg, block, off, buffer, size); +// } else { +// return lfs_rambd_prog(cfg, block, off, buffer, size); +// } +//} +// +//static int lfs_testbd_rawerase(const struct lfs_config *cfg, +// lfs_block_t block) { +// lfs_testbd_t *bd = cfg->context; +// if (bd->persist) { +// return lfs_filebd_erase(cfg, block); +// } else { +// return lfs_rambd_erase(cfg, block); +// } +//} +// +//static int lfs_testbd_rawsync(const struct lfs_config *cfg) { +// lfs_testbd_t *bd = cfg->context; +// if (bd->persist) { +// return lfs_filebd_sync(cfg); +// } else { +// return lfs_rambd_sync(cfg); +// } +//} -static int lfs_testbd_rawsync(const struct lfs_config *cfg) { - lfs_testbd_t *bd = cfg->context; - if (bd->persist) { - return lfs_filebd_sync(cfg); - } else { - return lfs_rambd_sync(cfg); - } -} -/// block device API /// + +// block device API + int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { LFS_TESTBD_TRACE("lfs_testbd_read(%p, " @@ -171,17 +217,27 @@ int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, LFS_ASSERT(size % cfg->read_size == 0); LFS_ASSERT(off+size <= cfg->block_size); - // block bad? - if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles && - bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) { - LFS_TESTBD_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT); - return LFS_ERR_CORRUPT; + // get the block + const lfs_testbd_block_t *b = lfs_testbd_getblock(bd, block); + if (b) { + // block bad? + if (bd->cfg->erase_cycles && b->wear >= bd->cfg->erase_cycles && + bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) { + LFS_TESTBD_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + } + + // read data + memcpy(buffer, &b->data[off], size); + } else { + // zero for consistency + memset(buffer, + (bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0, + size); } - // read - int err = lfs_testbd_rawread(cfg, block, off, buffer, size); - LFS_TESTBD_TRACE("lfs_testbd_read -> %d", err); - return err; + LFS_TESTBD_TRACE("lfs_testbd_read -> %d", 0); + return 0; } int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, @@ -197,8 +253,15 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, LFS_ASSERT(size % cfg->prog_size == 0); LFS_ASSERT(off+size <= cfg->block_size); + // get the block + lfs_testbd_block_t *b = lfs_testbd_mutblock(bd, block, cfg->block_size); + if (!b) { + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + // block bad? - if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) { + if (bd->cfg->erase_cycles && b->wear >= bd->cfg->erase_cycles) { if (bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_PROGERROR) { LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT); @@ -212,54 +275,34 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, } } - // emulate an erase value? + // were we erased properly? if (bd->cfg->erase_value != -1) { - int err = lfs_testbd_rawread(cfg, block, 0, - bd->scratch, cfg->block_size); - if (err) { - LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err); - return err; - } - - // assert that program block was erased for (lfs_off_t i = 0; i < size; i++) { - LFS_ASSERT(bd->scratch[off+i] == bd->cfg->erase_value); - } - - memcpy(&bd->scratch[off], buffer, size); - - err = lfs_testbd_rawerase(cfg, block); - if (err) { - LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err); - return err; - } - - err = lfs_testbd_rawprog(cfg, block, 0, - bd->scratch, cfg->block_size); - if (err) { - LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err); - return err; - } - } else { - // prog - int err = lfs_testbd_rawprog(cfg, block, off, buffer, size); - if (err) { - LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err); - return err; + LFS_ASSERT(b->data[off+i] == bd->cfg->erase_value); } } + // prog data + memcpy(&b->data[off], buffer, size); + // lose power? if (bd->power_cycles > 0) { bd->power_cycles -= 1; if (bd->power_cycles == 0) { - // sync to make sure we persist the last changes - LFS_ASSERT(lfs_testbd_rawsync(cfg) == 0); // simulate power loss - exit(33); + bd->cfg->powerloss_cb(bd->cfg->powerloss_data); } } +// // track power-loss branch? +// if (bd->cfg->track_branches) { +// int err = lfs_testbd_trackbranch(bd); +// if (err) { +// LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err); +// return err; +// } +// } + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0); return 0; } @@ -271,9 +314,16 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { // check if erase is valid LFS_ASSERT(block < cfg->block_count); + // get the block + lfs_testbd_block_t *b = lfs_testbd_mutblock(bd, block, cfg->block_size); + if (!b) { + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + // block bad? if (bd->cfg->erase_cycles) { - if (bd->wear[block] >= bd->cfg->erase_cycles) { + if (b->wear >= bd->cfg->erase_cycles) { if (bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_ERASEERROR) { LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT); @@ -285,70 +335,69 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { } } else { // mark wear - bd->wear[block] += 1; + b->wear += 1; } } // emulate an erase value? if (bd->cfg->erase_value != -1) { - memset(bd->scratch, bd->cfg->erase_value, cfg->block_size); - - int err = lfs_testbd_rawerase(cfg, block); - if (err) { - LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", err); - return err; - } - - err = lfs_testbd_rawprog(cfg, block, 0, - bd->scratch, cfg->block_size); - if (err) { - LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", err); - return err; - } - } else { - // erase - int err = lfs_testbd_rawerase(cfg, block); - if (err) { - LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", err); - return err; - } + memset(b->data, bd->cfg->erase_value, cfg->block_size); } // lose power? if (bd->power_cycles > 0) { bd->power_cycles -= 1; if (bd->power_cycles == 0) { - // sync to make sure we persist the last changes - LFS_ASSERT(lfs_testbd_rawsync(cfg) == 0); // simulate power loss - exit(33); + bd->cfg->powerloss_cb(bd->cfg->powerloss_data); } } +// // track power-loss branch? +// if (bd->cfg->track_branches) { +// int err = lfs_testbd_trackbranch(bd); +// if (err) { +// LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err); +// return err; +// } +// } + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0); return 0; } int lfs_testbd_sync(const struct lfs_config *cfg) { LFS_TESTBD_TRACE("lfs_testbd_sync(%p)", (void*)cfg); - int err = lfs_testbd_rawsync(cfg); - LFS_TESTBD_TRACE("lfs_testbd_sync -> %d", err); - return err; + + // do nothing + (void)cfg; + + LFS_TESTBD_TRACE("lfs_testbd_sync -> %d", 0); + return 0; } -/// simulated wear operations /// +// simulated wear operations + lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, lfs_block_t block) { LFS_TESTBD_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block); lfs_testbd_t *bd = cfg->context; // check if block is valid - LFS_ASSERT(bd->cfg->erase_cycles); LFS_ASSERT(block < cfg->block_count); - LFS_TESTBD_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]); - return bd->wear[block]; + // get the wear + lfs_testbd_wear_t wear; + const lfs_testbd_block_t *b = lfs_testbd_getblock(bd, block); + if (b) { + wear = b->wear; + } else { + wear = 0; + } + + LFS_TESTBD_TRACE("lfs_testbd_getwear -> %"PRIu32, wear); + return wear; } int lfs_testbd_setwear(const struct lfs_config *cfg, @@ -357,11 +406,58 @@ int lfs_testbd_setwear(const struct lfs_config *cfg, lfs_testbd_t *bd = cfg->context; // check if block is valid - LFS_ASSERT(bd->cfg->erase_cycles); LFS_ASSERT(block < cfg->block_count); - bd->wear[block] = wear; + // set the wear + lfs_testbd_block_t *b = lfs_testbd_mutblock(bd, block, cfg->block_size); + if (!b) { + LFS_TESTBD_TRACE("lfs_testbd_setwear -> %"PRIu32, LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + b->wear = wear; - LFS_TESTBD_TRACE("lfs_testbd_setwear -> %d", 0); + LFS_TESTBD_TRACE("lfs_testbd_setwear -> %"PRIu32, 0); return 0; } + +lfs_testbd_spowercycles_t lfs_testbd_getpowercycles( + const struct lfs_config *cfg) { + LFS_TESTBD_TRACE("lfs_testbd_getpowercycles(%p)", (void*)cfg); + lfs_testbd_t *bd = cfg->context; + + LFS_TESTBD_TRACE("lfs_testbd_getpowercycles -> %"PRIi32, bd->power_cycles); + return bd->power_cycles; +} + +int lfs_testbd_setpowercycles(const struct lfs_config *cfg, + lfs_testbd_powercycles_t power_cycles) { + LFS_TESTBD_TRACE("lfs_testbd_setpowercycles(%p, %"PRIi32")", + (void*)cfg, power_cycles); + lfs_testbd_t *bd = cfg->context; + + bd->power_cycles = power_cycles; + + LFS_TESTBD_TRACE("lfs_testbd_getpowercycles -> %d", 0); + return 0; +} + +//int lfs_testbd_getbranch(const struct lfs_config *cfg, +// lfs_testbd_powercycles_t branch, lfs_testbd_t *bd) { +// LFS_TESTBD_TRACE("lfs_testbd_getbranch(%p, %zu, %p)", +// (void*)cfg, branch, bd); +// lfs_testbd_t *bd = cfg->context; +// +// // TODO +// +// LFS_TESTBD_TRACE("lfs_testbd_getbranch -> %d", 0); +// return 0; +//} + +lfs_testbd_spowercycles_t lfs_testbd_getbranchcount( + const struct lfs_config *cfg) { + LFS_TESTBD_TRACE("lfs_testbd_getbranchcount(%p)", (void*)cfg); + lfs_testbd_t *bd = cfg->context; + + LFS_TESTBD_TRACE("lfs_testbd_getbranchcount -> %"PRIu32, bd->branch_count); + return bd->branch_count; +} diff --git a/bd/lfs_testbd.h b/bd/lfs_testbd.h index 3eafb8f..9b51cd8 100644 --- a/bd/lfs_testbd.h +++ b/bd/lfs_testbd.h @@ -29,23 +29,33 @@ extern "C" #endif #endif -// Mode determining how "bad blocks" behave during testing. This simulates +// Mode determining how "bad-blocks" behave during testing. This simulates // some real-world circumstances such as progs not sticking (prog-noop), // a readonly disk (erase-noop), and ECC failures (read-error). // // Not that read-noop is not allowed. Read _must_ return a consistent (but // may be arbitrary) value on every read. -enum lfs_testbd_badblock_behavior { +typedef enum lfs_testbd_badblock_behavior { LFS_TESTBD_BADBLOCK_PROGERROR, LFS_TESTBD_BADBLOCK_ERASEERROR, LFS_TESTBD_BADBLOCK_READERROR, LFS_TESTBD_BADBLOCK_PROGNOOP, LFS_TESTBD_BADBLOCK_ERASENOOP, -}; +} lfs_testbd_badblock_behavior_t; + +// Mode determining how power-loss behaves during testing. For now this +// only supports a noop behavior, leaving the data on-disk untouched. +typedef enum lfs_testbd_powerloss_behavior { + LFS_TESTBD_POWERLOSS_NOOP, +} lfs_testbd_powerloss_behavior_t; // Type for measuring wear typedef uint32_t lfs_testbd_wear_t; -typedef int32_t lfs_testbd_swear_t; +typedef int32_t lfs_testbd_swear_t; + +// Type for tracking power-cycles +typedef uint32_t lfs_testbd_powercycles_t; +typedef int32_t lfs_testbd_spowercycles_t; // testbd config, this is required for testing struct lfs_testbd_config { @@ -55,42 +65,77 @@ struct lfs_testbd_config { int32_t erase_value; // Number of erase cycles before a block becomes "bad". The exact behavior - // of bad blocks is controlled by the badblock_mode. + // of bad blocks is controlled by badblock_behavior. uint32_t erase_cycles; - // The mode determining how bad blocks fail - uint8_t badblock_behavior; + // The mode determining how bad-blocks fail + lfs_testbd_badblock_behavior_t badblock_behavior; - // Number of write operations (erase/prog) before forcefully killing - // the program with exit. Simulates power-loss. 0 disables. - uint32_t power_cycles; + // Number of write operations (erase/prog) before triggering a power-loss. + // power_cycles=0 disables this. The exact behavior of power-loss is + // controlled by a combination of powerloss_behavior and powerloss_cb. + lfs_testbd_powercycles_t power_cycles; - // Optional buffer for RAM block device. - void *buffer; + // The mode determining how power-loss affects disk + lfs_testbd_powerloss_behavior_t powerloss_behavior; - // Optional buffer for wear. - void *wear_buffer; + // Function to call to emulate power-loss. The exact behavior of power-loss + // is up to the runner to provide. + void (*powerloss_cb)(void*); - // Optional buffer for scratch memory, needed when erase_value != -1. - void *scratch_buffer; + // Data for power-loss callback + void *powerloss_data; + + // True to track when power-loss could have occured. Note this involves + // heavy memory usage! + bool track_branches; + +// // Optional buffer for RAM block device. +// void *buffer; +// +// // Optional buffer for wear. +// void *wear_buffer; +// +// // Optional buffer for scratch memory, needed when erase_value != -1. +// void *scratch_buffer; }; +// A reference counted block +typedef struct lfs_testbd_block { + uint32_t rc; + lfs_testbd_wear_t wear; + + uint8_t data[]; +} lfs_testbd_block_t; + // testbd state typedef struct lfs_testbd { - union { - struct { - lfs_filebd_t bd; - } file; - struct { - lfs_rambd_t bd; - struct lfs_rambd_config cfg; - } ram; - } u; - - bool persist; + // array of copy-on-write blocks + lfs_testbd_block_t **blocks; uint32_t power_cycles; - lfs_testbd_wear_t *wear; - uint8_t *scratch; + + // array of tracked branches + struct lfs_testbd *branches; + lfs_testbd_powercycles_t branch_count; + lfs_testbd_powercycles_t branch_capacity; + + // TODO file? + + +// union { +// struct { +// lfs_filebd_t bd; +// } file; +// struct { +// lfs_rambd_t bd; +// struct lfs_rambd_config cfg; +// } ram; +// } u; +// +// bool persist; +// uint32_t power_cycles; +// lfs_testbd_wear_t *wear; +// uint8_t *scratch; const struct lfs_testbd_config *cfg; } lfs_testbd_t; @@ -139,6 +184,22 @@ lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, int lfs_testbd_setwear(const struct lfs_config *cfg, lfs_block_t block, lfs_testbd_wear_t wear); +// Get the remaining power-cycles +lfs_testbd_spowercycles_t lfs_testbd_getpowercycles( + const struct lfs_config *cfg); + +// Manually set the remaining power-cycles +int lfs_testbd_setpowercycles(const struct lfs_config *cfg, + lfs_testbd_powercycles_t power_cycles); + +// Get a power-loss branch, requires track_branches=true +int lfs_testbd_getbranch(const struct lfs_config *cfg, + lfs_testbd_powercycles_t branch, lfs_testbd_t *bd); + +// Get the current number of power-loss branches +lfs_testbd_spowercycles_t lfs_testbd_getbranchcount( + const struct lfs_config *cfg); + #ifdef __cplusplus } /* extern "C" */ diff --git a/lfs.h b/lfs.h index 3fc1e98..9bdfaa0 100644 --- a/lfs.h +++ b/lfs.h @@ -8,8 +8,6 @@ #ifndef LFS_H #define LFS_H -#include -#include #include "lfs_util.h" #ifdef __cplusplus diff --git a/lfs_util.h b/lfs_util.h index 0cbc2a3..3971882 100644 --- a/lfs_util.h +++ b/lfs_util.h @@ -23,6 +23,7 @@ // System includes #include #include +#include #include #include diff --git a/runners/test_runner.c b/runners/test_runner.c index cc932b7..392915a 100644 --- a/runners/test_runner.c +++ b/runners/test_runner.c @@ -5,6 +5,7 @@ #include #include #include +#include // test suites in a custom ld section @@ -75,7 +76,7 @@ intmax_t test_define(size_t define) { exit(-1); } -static void test_define_geometry(const struct test_geometry *geometry) { +static void define_geometry(const struct test_geometry *geometry) { test_geometry_defines = geometry->defines; } @@ -98,7 +99,7 @@ static void test_define_overrides( } } -static void test_define_suite(const struct test_suite *suite) { +static void define_suite(const struct test_suite *suite) { test_define_names = suite->define_names; test_define_count = suite->define_count; @@ -123,7 +124,7 @@ static void test_define_suite(const struct test_suite *suite) { } } -static void test_define_perm( +static void define_perm( const struct test_suite *suite, const struct test_case *case_, size_t perm) { @@ -136,12 +137,110 @@ static void test_define_perm( } -// other miscellany -static const char *test_suite = NULL; -static const char *test_case = NULL; -static size_t test_perm = -1; +// a quick encoding scheme for sequences of power-loss +static void leb16_print( + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count) { + for (size_t i = 0; i < cycle_count; i++) { + lfs_testbd_powercycles_t x = cycles[i]; + while (true) { + lfs_testbd_powercycles_t nibble = (x & 0xf) | (x > 0xf ? 0x10 : 0); + printf("%c", (nibble < 10) ? '0'+nibble : 'a'+nibble-10); + if (x <= 0xf) { + break; + } + x >>= 4; + } + } +} + +static size_t leb16_parse(const char *s, char **tail, + lfs_testbd_powercycles_t **cycles) { + // first lets count how many number we're dealing with + size_t count = 0; + size_t len = 0; + for (size_t i = 0;; i++) { + if ((s[i] >= '0' && s[i] <= '9') + || (s[i] >= 'a' && s[i] <= 'f')) { + len = i+1; + count += 1; + } else if ((s[i] >= 'g' && s[i] <= 'v')) { + // do nothing + } else { + break; + } + } + + // then parse + lfs_testbd_powercycles_t *cycles_ = malloc( + count * sizeof(lfs_testbd_powercycles_t)); + size_t i = 0; + lfs_testbd_powercycles_t x = 0; + size_t k = 0; + for (size_t j = 0; j < len; j++) { + lfs_testbd_powercycles_t nibble = s[j]; + nibble = (nibble < 'a') ? nibble-'0' : nibble-'a'+10; + x |= (nibble & 0xf) << (4*k); + k += 1; + if (!(nibble & 0x10)) { + cycles_[i] = x; + i += 1; + x = 0; + k = 0; + } + } + + if (tail) { + *tail = (char*)s + len; + } + *cycles = cycles_; + return count; +} + + +// test state +typedef struct test_powerloss { + char short_name; + const char *long_name; + + void (*run)( + const struct test_suite *suite, + const struct test_case *case_, + size_t perm, + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count); + const lfs_testbd_powercycles_t *cycles; + size_t cycle_count; +} test_powerloss_t; + +static void run_powerloss_none( + const struct test_suite *suite, + const struct test_case *case_, + size_t perm, + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count); +static const test_powerloss_t *test_powerlosses = (const test_powerloss_t[]){ + {'0', "none", run_powerloss_none, NULL, 0}, +}; +static size_t test_powerloss_count = 1; + + +typedef struct test_id { + const char *suite; + const char *case_; + size_t perm; + const lfs_testbd_powercycles_t *cycles; + size_t cycle_count; +} test_id_t; + +static const test_id_t *test_ids = (const test_id_t[]) { + {NULL, NULL, -1, NULL, 0}, +}; +static size_t test_id_count = 1; + + static const char *test_geometry = NULL; -static test_types_t test_types = 0; + static size_t test_start = 0; static size_t test_stop = -1; static size_t test_step = 1; @@ -149,243 +248,294 @@ static size_t test_step = 1; static const char *test_disk = NULL; FILE *test_trace = NULL; -// note, these skips are different than filtered tests -static bool test_suite_skip(const struct test_suite *suite) { - return (test_suite && strcmp(suite->name, test_suite) != 0) - || (test_types && (suite->types & test_types) == 0); -} -static bool test_case_skip(const struct test_case *case_) { - return (test_case && strcmp(case_->name, test_case) != 0) - || (test_types && (case_->types & test_types) == 0); -} - -static bool test_perm_skip(size_t perm) { - size_t geom_perm = perm % TEST_GEOMETRY_COUNT; - return (test_perm != (size_t)-1 && perm != test_perm) - || (test_geometry && (strcmp( - test_geometries[geom_perm].name, - test_geometry) != 0)); -} - -static bool test_step_skip(size_t step) { - return !(step >= test_start - && step < test_stop - && (step-test_start) % test_step == 0); -} - -static void test_case_permcount( +// how many permutations are there actually in a test case +static void count_perms( const struct test_suite *suite, const struct test_case *case_, + size_t perm, + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count, size_t *perms, size_t *filtered) { + (void)cycle_count; size_t perms_ = 0; size_t filtered_ = 0; - for (size_t perm = 0; - perm < TEST_GEOMETRY_COUNT - * case_->permutations; - perm++) { - if (test_perm_skip(perm)) { + for (size_t p = 0; p < (cycles ? 1 : test_powerloss_count); p++) { + if (!cycles + && test_powerlosses[p].short_name != '0' + && !(case_->flags & TEST_REENTRANT)) { continue; } - perms_ += 1; - - // setup defines - size_t case_perm = perm / TEST_GEOMETRY_COUNT; - size_t geom_perm = perm % TEST_GEOMETRY_COUNT; - test_define_perm(suite, case_, case_perm); - test_define_geometry(&test_geometries[geom_perm]); - - if (case_->filter) { - if (!case_->filter()) { + size_t perm_ = 0; + for (size_t g = 0; g < TEST_GEOMETRY_COUNT; g++) { + if (test_geometry && strcmp( + test_geometries[g].name, test_geometry) != 0) { continue; } - } - filtered_ += 1; + for (size_t k = 0; k < case_->permutations; k++) { + perm_ += 1; + + if (perm != (size_t)-1 && perm_ != perm) { + continue; + } + + perms_ += 1; + + // setup defines + define_perm(suite, case_, k); + define_geometry(&test_geometries[g]); + + if (case_->filter && !case_->filter()) { + continue; + } + + filtered_ += 1; + } + } } *perms += perms_; *filtered += filtered_; -} +} // operations we can do static void summary(void) { printf("%-36s %7s %7s %7s %11s\n", - "", "types", "suites", "cases", "perms"); + "", "flags", "suites", "cases", "perms"); size_t cases = 0; - test_types_t types = 0; + test_flags_t flags = 0; size_t perms = 0; size_t filtered = 0; - for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { - if (test_suite_skip(&test_suites[i])) { - continue; - } - test_define_suite(&test_suites[i]); - - for (size_t j = 0; j < test_suites[i].case_count; j++) { - if (test_case_skip(&test_suites[i].cases[j])) { + for (size_t t = 0; t < test_id_count; t++) { + for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { + if (test_ids[t].suite && strcmp( + test_suites[i].name, test_ids[t].suite) != 0) { continue; } - test_case_permcount(&test_suites[i], &test_suites[i].cases[j], - &perms, &filtered); - } + define_suite(&test_suites[i]); - cases += test_suites[i].case_count; - types |= test_suites[i].types; + for (size_t j = 0; j < test_suites[i].case_count; j++) { + if (test_ids[t].case_ && strcmp( + test_suites[i].cases[j].name, test_ids[t].case_) != 0) { + continue; + } + + count_perms(&test_suites[i], &test_suites[i].cases[j], + test_ids[t].perm, + test_ids[t].cycles, + test_ids[t].cycle_count, + &perms, &filtered); + } + + cases += test_suites[i].case_count; + flags |= test_suites[i].flags; + } } char perm_buf[64]; sprintf(perm_buf, "%zu/%zu", filtered, perms); - char type_buf[64]; - sprintf(type_buf, "%s%s", - (types & TEST_NORMAL) ? "n" : "", - (types & TEST_REENTRANT) ? "r" : ""); + char flag_buf[64]; + sprintf(flag_buf, "%s%s", + (flags & TEST_REENTRANT) ? "r" : "", + (!flags) ? "-" : ""); printf("%-36s %7s %7zu %7zu %11s\n", "TOTAL", - type_buf, + flag_buf, TEST_SUITE_COUNT, cases, perm_buf); } static void list_suites(void) { - printf("%-36s %7s %7s %11s\n", "suite", "types", "cases", "perms"); - for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { - if (test_suite_skip(&test_suites[i])) { - continue; - } + printf("%-36s %7s %7s %11s\n", "suite", "flags", "cases", "perms"); - test_define_suite(&test_suites[i]); - - size_t perms = 0; - size_t filtered = 0; - for (size_t j = 0; j < test_suites[i].case_count; j++) { - if (test_case_skip(&test_suites[i].cases[j])) { + for (size_t t = 0; t < test_id_count; t++) { + for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { + if (test_ids[t].suite && strcmp( + test_suites[i].name, test_ids[t].suite) != 0) { continue; } - test_case_permcount(&test_suites[i], &test_suites[i].cases[j], - &perms, &filtered); - } - - char perm_buf[64]; - sprintf(perm_buf, "%zu/%zu", filtered, perms); - char type_buf[64]; - sprintf(type_buf, "%s%s", - (test_suites[i].types & TEST_NORMAL) ? "n" : "", - (test_suites[i].types & TEST_REENTRANT) ? "r" : ""); - printf("%-36s %7s %7zu %11s\n", - test_suites[i].id, - type_buf, - test_suites[i].case_count, - perm_buf); - } -} - -static void list_cases(void) { - printf("%-36s %7s %11s\n", "case", "types", "perms"); - for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { - if (test_suite_skip(&test_suites[i])) { - continue; - } - - test_define_suite(&test_suites[i]); - - for (size_t j = 0; j < test_suites[i].case_count; j++) { - if (test_case_skip(&test_suites[i].cases[j])) { - continue; - } + define_suite(&test_suites[i]); size_t perms = 0; size_t filtered = 0; - test_case_permcount(&test_suites[i], &test_suites[i].cases[j], - &perms, &filtered); - test_types_t types = test_suites[i].cases[j].types; + + for (size_t j = 0; j < test_suites[i].case_count; j++) { + if (test_ids[t].case_ && strcmp( + test_suites[i].cases[j].name, test_ids[t].case_) != 0) { + continue; + } + + count_perms(&test_suites[i], &test_suites[i].cases[j], + test_ids[t].perm, + test_ids[t].cycles, + test_ids[t].cycle_count, + &perms, &filtered); + } char perm_buf[64]; sprintf(perm_buf, "%zu/%zu", filtered, perms); - char type_buf[64]; - sprintf(type_buf, "%s%s", - (types & TEST_NORMAL) ? "n" : "", - (types & TEST_REENTRANT) ? "r" : ""); - printf("%-36s %7s %11s\n", - test_suites[i].cases[j].id, - type_buf, + char flag_buf[64]; + sprintf(flag_buf, "%s%s", + (test_suites[i].flags & TEST_REENTRANT) ? "r" : "", + (!test_suites[i].flags) ? "-" : ""); + printf("%-36s %7s %7zu %11s\n", + test_suites[i].id, + flag_buf, + test_suites[i].case_count, perm_buf); } } } -static void list_paths(void) { - for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { - if (test_suite_skip(&test_suites[i])) { - continue; - } +static void list_cases(void) { + printf("%-36s %7s %11s\n", "case", "flags", "perms"); - for (size_t j = 0; j < test_suites[i].case_count; j++) { - if (test_case_skip(&test_suites[i].cases[j])) { + for (size_t t = 0; t < test_id_count; t++) { + for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { + if (test_ids[t].suite && strcmp( + test_suites[i].name, test_ids[t].suite) != 0) { continue; } - printf("%-36s %-36s\n", - test_suites[i].cases[j].id, - test_suites[i].cases[j].path); + define_suite(&test_suites[i]); + + for (size_t j = 0; j < test_suites[i].case_count; j++) { + if (test_ids[t].case_ && strcmp( + test_suites[i].cases[j].name, test_ids[t].case_) != 0) { + continue; + } + + size_t perms = 0; + size_t filtered = 0; + + count_perms(&test_suites[i], &test_suites[i].cases[j], + test_ids[t].perm, + test_ids[t].cycles, + test_ids[t].cycle_count, + &perms, &filtered); + + char perm_buf[64]; + sprintf(perm_buf, "%zu/%zu", filtered, perms); + char flag_buf[64]; + sprintf(flag_buf, "%s%s", + (test_suites[i].cases[j].flags & TEST_REENTRANT) + ? "r" : "", + (!test_suites[i].cases[j].flags) + ? "-" : ""); + printf("%-36s %7s %11s\n", + test_suites[i].cases[j].id, + flag_buf, + perm_buf); + } + } + } +} + +static void list_paths(void) { + for (size_t t = 0; t < test_id_count; t++) { + for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { + if (test_ids[t].suite && strcmp( + test_suites[i].name, test_ids[t].suite) != 0) { + continue; + } + + for (size_t j = 0; j < test_suites[i].case_count; j++) { + if (test_ids[t].case_ && strcmp( + test_suites[i].cases[j].name, test_ids[t].case_) != 0) { + continue; + } + + printf("%-36s %-36s\n", + test_suites[i].cases[j].id, + test_suites[i].cases[j].path); + } } } } static void list_defines(void) { - for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { - if (test_suite_skip(&test_suites[i])) { - continue; - } - - test_define_suite(&test_suites[i]); - - for (size_t j = 0; j < test_suites[i].case_count; j++) { - if (test_case_skip(&test_suites[i].cases[j])) { + for (size_t t = 0; t < test_id_count; t++) { + for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { + if (test_ids[t].suite && strcmp( + test_suites[i].name, test_ids[t].suite) != 0) { continue; } - for (size_t perm = 0; - perm < TEST_GEOMETRY_COUNT - * test_suites[i].cases[j].permutations; - perm++) { - if (test_perm_skip(perm)) { + define_suite(&test_suites[i]); + + for (size_t j = 0; j < test_suites[i].case_count; j++) { + if (test_ids[t].case_ && strcmp( + test_suites[i].cases[j].name, test_ids[t].case_) != 0) { continue; } - // setup defines - size_t case_perm = perm / TEST_GEOMETRY_COUNT; - size_t geom_perm = perm % TEST_GEOMETRY_COUNT; - test_define_perm(&test_suites[i], - &test_suites[i].cases[j], case_perm); - test_define_geometry(&test_geometries[geom_perm]); + for (size_t p = 0; + p < (test_ids[t].cycles ? 1 : test_powerloss_count); + p++) { + if (!test_ids[t].cycles + && test_powerlosses[p].short_name != '0' + && !(test_suites[i].cases[j].flags + & TEST_REENTRANT)) { + continue; + } - // print the case - char id_buf[256]; - sprintf(id_buf, "%s#%zu", test_suites[i].cases[j].id, perm); - printf("%-36s ", id_buf); + size_t perm_ = 0; + for (size_t g = 0; g < TEST_GEOMETRY_COUNT; g++) { + if (test_geometry && strcmp( + test_geometries[g].name, test_geometry) != 0) { + continue; + } - // special case for the current geometry - printf("GEOMETRY=%s ", test_geometries[geom_perm].name); + for (size_t k = 0; + k < test_suites[i].cases[j].permutations; + k++) { + perm_ += 1; - // print each define - for (size_t k = 0; k < test_suites[i].define_count; k++) { - if (test_suites[i].cases[j].defines - && test_suites[i].cases[j].defines[case_perm][k]) { - printf("%s=%jd ", - test_suites[i].define_names[k], - test_define(k)); + if (test_ids[t].perm != (size_t)-1 + && perm_ != test_ids[t].perm) { + continue; + } + + // setup defines + define_perm(&test_suites[i], + &test_suites[i].cases[j], + k); + define_geometry(&test_geometries[g]); + + // print the case + char id_buf[256]; + sprintf(id_buf, "%s#%zu", + test_suites[i].cases[j].id, perm_); + printf("%-36s ", id_buf); + + // special case for the current geometry + printf("GEOMETRY=%s ", test_geometries[g].name); + + // print each define + for (size_t l = 0; + l < test_suites[i].define_count; + l++) { + if (test_suites[i].cases[j].defines + && test_suites[i].cases[j] + .defines[k][l]) { + printf("%s=%jd ", + test_suites[i].define_names[l], + test_define(l)); + } + } + printf("\n"); + } } } - printf("\n"); } } } @@ -399,7 +549,7 @@ static void list_geometries(void) { continue; } - test_define_geometry(&test_geometries[i]); + define_geometry(&test_geometries[i]); printf("%-36s ", test_geometries[i].name); // print each define @@ -424,96 +574,420 @@ static void list_defaults(void) { printf("\n"); } -static void run(void) { - size_t step = 0; - for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { - if (test_suite_skip(&test_suites[i])) { + + +// scenarios to run tests under power-loss + +static void run_powerloss_none( + const struct test_suite *suite, + const struct test_case *case_, + size_t perm, + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count) { + (void)cycles; + (void)cycle_count; + (void)suite; + + // create block device and configuration + lfs_testbd_t bd; + + struct lfs_config cfg = { + .context = &bd, + .read = lfs_testbd_read, + .prog = lfs_testbd_prog, + .erase = lfs_testbd_erase, + .sync = lfs_testbd_sync, + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .block_size = BLOCK_SIZE, + .block_count = BLOCK_COUNT, + .block_cycles = BLOCK_CYCLES, + .cache_size = CACHE_SIZE, + .lookahead_size = LOOKAHEAD_SIZE, + }; + + struct lfs_testbd_config bdcfg = { + .erase_value = ERASE_VALUE, + .erase_cycles = ERASE_CYCLES, + .badblock_behavior = BADBLOCK_BEHAVIOR, + }; + + int err = lfs_testbd_createcfg(&cfg, test_disk, &bdcfg); + if (err) { + fprintf(stderr, "error: could not create block device: %d\n", err); + exit(-1); + } + + // run the test + printf("running %s#%zu\n", case_->id, perm); + + case_->run(&cfg); + + printf("finished %s#%zu\n", case_->id, perm); + + // cleanup + err = lfs_testbd_destroy(&cfg); + if (err) { + fprintf(stderr, "error: could not destroy block device: %d\n", err); + exit(-1); + } +} + +static void powerloss_longjmp(void *c) { + jmp_buf *powerloss_jmp = c; + longjmp(*powerloss_jmp, 1); +} + +static void run_powerloss_linear( + const struct test_suite *suite, + const struct test_case *case_, + size_t perm, + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count) { + (void)cycles; + (void)cycle_count; + (void)suite; + + // create block device and configuration + lfs_testbd_t bd; + jmp_buf powerloss_jmp; + volatile lfs_testbd_powercycles_t i = 1; + + struct lfs_config cfg = { + .context = &bd, + .read = lfs_testbd_read, + .prog = lfs_testbd_prog, + .erase = lfs_testbd_erase, + .sync = lfs_testbd_sync, + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .block_size = BLOCK_SIZE, + .block_count = BLOCK_COUNT, + .block_cycles = BLOCK_CYCLES, + .cache_size = CACHE_SIZE, + .lookahead_size = LOOKAHEAD_SIZE, + }; + + struct lfs_testbd_config bdcfg = { + .erase_value = ERASE_VALUE, + .erase_cycles = ERASE_CYCLES, + .badblock_behavior = BADBLOCK_BEHAVIOR, + .power_cycles = i, + .powerloss_behavior = POWERLOSS_BEHAVIOR, + .powerloss_cb = powerloss_longjmp, + .powerloss_data = &powerloss_jmp, + }; + + int err = lfs_testbd_createcfg(&cfg, test_disk, &bdcfg); + if (err) { + fprintf(stderr, "error: could not create block device: %d\n", err); + exit(-1); + } + + // run the test, increasing power-cycles as power-loss events occur + printf("running %s#%zu\n", case_->id, perm); + + while (true) { + if (!setjmp(powerloss_jmp)) { + case_->run(&cfg); + break; + } + + // power-loss! + printf("powerloss %s#%zu#", case_->id, perm); + for (lfs_testbd_powercycles_t j = 1; j <= i; j++) { + leb16_print(&j, 1); + } + printf("\n"); + + i += 1; + lfs_testbd_setpowercycles(&cfg, i); + } + + printf("finished %s#%zu\n", case_->id, perm); + + // cleanup + err = lfs_testbd_destroy(&cfg); + if (err) { + fprintf(stderr, "error: could not destroy block device: %d\n", err); + exit(-1); + } +} + +static void run_powerloss_exponential( + const struct test_suite *suite, + const struct test_case *case_, + size_t perm, + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count) { + (void)cycles; + (void)cycle_count; + (void)suite; + + // create block device and configuration + lfs_testbd_t bd; + jmp_buf powerloss_jmp; + volatile lfs_testbd_powercycles_t i = 1; + + struct lfs_config cfg = { + .context = &bd, + .read = lfs_testbd_read, + .prog = lfs_testbd_prog, + .erase = lfs_testbd_erase, + .sync = lfs_testbd_sync, + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .block_size = BLOCK_SIZE, + .block_count = BLOCK_COUNT, + .block_cycles = BLOCK_CYCLES, + .cache_size = CACHE_SIZE, + .lookahead_size = LOOKAHEAD_SIZE, + }; + + struct lfs_testbd_config bdcfg = { + .erase_value = ERASE_VALUE, + .erase_cycles = ERASE_CYCLES, + .badblock_behavior = BADBLOCK_BEHAVIOR, + .power_cycles = i, + .powerloss_behavior = POWERLOSS_BEHAVIOR, + .powerloss_cb = powerloss_longjmp, + .powerloss_data = &powerloss_jmp, + }; + + int err = lfs_testbd_createcfg(&cfg, test_disk, &bdcfg); + if (err) { + fprintf(stderr, "error: could not create block device: %d\n", err); + exit(-1); + } + + // run the test, increasing power-cycles as power-loss events occur + printf("running %s#%zu\n", case_->id, perm); + + while (true) { + if (!setjmp(powerloss_jmp)) { + case_->run(&cfg); + break; + } + + // power-loss! + printf("powerloss %s#%zu#", case_->id, perm); + for (lfs_testbd_powercycles_t j = 1; j <= i; j *= 2) { + leb16_print(&j, 1); + } + printf("\n"); + + i *= 2; + lfs_testbd_setpowercycles(&cfg, i); + } + + printf("finished %s#%zu\n", case_->id, perm); + + // cleanup + err = lfs_testbd_destroy(&cfg); + if (err) { + fprintf(stderr, "error: could not destroy block device: %d\n", err); + exit(-1); + } +} + +static void run_powerloss_cycles( + const struct test_suite *suite, + const struct test_case *case_, + size_t perm, + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count) { + (void)suite; + + // create block device and configuration + lfs_testbd_t bd; + jmp_buf powerloss_jmp; + volatile size_t i = 0; + + struct lfs_config cfg = { + .context = &bd, + .read = lfs_testbd_read, + .prog = lfs_testbd_prog, + .erase = lfs_testbd_erase, + .sync = lfs_testbd_sync, + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .block_size = BLOCK_SIZE, + .block_count = BLOCK_COUNT, + .block_cycles = BLOCK_CYCLES, + .cache_size = CACHE_SIZE, + .lookahead_size = LOOKAHEAD_SIZE, + }; + + struct lfs_testbd_config bdcfg = { + .erase_value = ERASE_VALUE, + .erase_cycles = ERASE_CYCLES, + .badblock_behavior = BADBLOCK_BEHAVIOR, + .power_cycles = (i < cycle_count) ? cycles[i] : 0, + .powerloss_behavior = POWERLOSS_BEHAVIOR, + .powerloss_cb = powerloss_longjmp, + .powerloss_data = &powerloss_jmp, + }; + + int err = lfs_testbd_createcfg(&cfg, test_disk, &bdcfg); + if (err) { + fprintf(stderr, "error: could not create block device: %d\n", err); + exit(-1); + } + + // run the test, increasing power-cycles as power-loss events occur + printf("running %s#%zu\n", case_->id, perm); + + while (true) { + if (!setjmp(powerloss_jmp)) { + case_->run(&cfg); + break; + } + + // power-loss! + assert(i <= cycle_count); + printf("powerloss %s#%zu#", case_->id, perm); + leb16_print(cycles, i+1); + printf("\n"); + + i += 1; + lfs_testbd_setpowercycles(&cfg, + (i < cycle_count) ? cycles[i] : 0); + } + + printf("finished %s#%zu\n", case_->id, perm); + + // cleanup + err = lfs_testbd_destroy(&cfg); + if (err) { + fprintf(stderr, "error: could not destroy block device: %d\n", err); + exit(-1); + } +} + +//static void run_powerloss_n(void *data, +// +//static void run_powerloss_incremental(void *data, + +const test_powerloss_t builtin_powerlosses[] = { + {'0', "none", run_powerloss_none, NULL, 0}, + {'e', "exponential", run_powerloss_exponential, NULL, 0}, + {'l', "linear", run_powerloss_linear, NULL, 0}, + //{'x', "exhaustive", run_powerloss_exhaustive} + {0, NULL, NULL, NULL, 0}, +}; + +const char *const builtin_powerlosses_help[] = { + "Run with no power-losses.", + "Run with linearly-decreasing power-losses.", + "Run with exponentially-decreasing power-losses.", + //"Run a all permutations of power-losses, this may take a while.", + "Run a all permutations of n power-losses.", + "Run a custom comma-separated set of power-losses.", + "Run a custom leb16-encoded set of power-losses.", +}; + +static void list_powerlosses(void) { + printf("%-24s %s\n", "scenario", "description"); + size_t i = 0; + for (; builtin_powerlosses[i].long_name; i++) { + printf("%c,%-22s %s\n", + builtin_powerlosses[i].short_name, + builtin_powerlosses[i].long_name, + builtin_powerlosses_help[i]); + } + + // a couple more options with special parsing + printf("%-24s %s\n", "1,2,3", builtin_powerlosses_help[i+0]); + printf("%-24s %s\n", "{1,2,3}", builtin_powerlosses_help[i+1]); + printf("%-24s %s\n", "#1248g1", builtin_powerlosses_help[i+2]); +} + + +// global test step count +static size_t step = 0; + +// run the tests +static void run_perms( + const struct test_suite *suite, + const struct test_case *case_, + size_t perm, + const lfs_testbd_powercycles_t *cycles, + size_t cycle_count) { + for (size_t p = 0; p < (cycles ? 1 : test_powerloss_count); p++) { + if (!cycles + && test_powerlosses[p].short_name != '0' + && !(case_->flags & TEST_REENTRANT)) { continue; } - test_define_suite(&test_suites[i]); - - for (size_t j = 0; j < test_suites[i].case_count; j++) { - if (test_case_skip(&test_suites[i].cases[j])) { + size_t perm_ = 0; + for (size_t g = 0; g < TEST_GEOMETRY_COUNT; g++) { + if (test_geometry && strcmp( + test_geometries[g].name, test_geometry) != 0) { continue; } - for (size_t perm = 0; - perm < TEST_GEOMETRY_COUNT - * test_suites[i].cases[j].permutations; - perm++) { - if (test_perm_skip(perm)) { + for (size_t k = 0; k < case_->permutations; k++) { + perm_ += 1; + + if (perm != (size_t)-1 && perm_ != perm) { continue; } - if (test_step_skip(step)) { + + if (!(step >= test_start + && step < test_stop + && (step-test_start) % test_step == 0)) { step += 1; continue; } step += 1; // setup defines - size_t case_perm = perm / TEST_GEOMETRY_COUNT; - size_t geom_perm = perm % TEST_GEOMETRY_COUNT; - test_define_perm(&test_suites[i], - &test_suites[i].cases[j], case_perm); - test_define_geometry(&test_geometries[geom_perm]); + define_perm(suite, case_, k); + define_geometry(&test_geometries[g]); // filter? - if (test_suites[i].cases[j].filter) { - if (!test_suites[i].cases[j].filter()) { - printf("skipped %s#%zu\n", - test_suites[i].cases[j].id, - perm); - continue; - } + if (case_->filter && !case_->filter()) { + printf("skipped %s#%zu\n", case_->id, perm_); + continue; } - // create block device and configuration - lfs_testbd_t bd; + if (cycles) { + run_powerloss_cycles( + suite, case_, perm_, + cycles, + cycle_count); + } else { + test_powerlosses[p].run( + suite, case_, perm_, + test_powerlosses[p].cycles, + test_powerlosses[p].cycle_count); + } + } + } + } +} - struct lfs_config cfg = { - .context = &bd, - .read = lfs_testbd_read, - .prog = lfs_testbd_prog, - .erase = lfs_testbd_erase, - .sync = lfs_testbd_sync, - .read_size = READ_SIZE, - .prog_size = PROG_SIZE, - .block_size = BLOCK_SIZE, - .block_count = BLOCK_COUNT, - .block_cycles = BLOCK_CYCLES, - .cache_size = CACHE_SIZE, - .lookahead_size = LOOKAHEAD_SIZE, - }; +static void run(void) { + for (size_t t = 0; t < test_id_count; t++) { + for (size_t i = 0; i < TEST_SUITE_COUNT; i++) { + if (test_ids[t].suite && strcmp( + test_suites[i].name, test_ids[t].suite) != 0) { + continue; + } - struct lfs_testbd_config bdcfg = { - .erase_value = ERASE_VALUE, - .erase_cycles = ERASE_CYCLES, - .badblock_behavior = BADBLOCK_BEHAVIOR, - .power_cycles = 0, - }; + define_suite(&test_suites[i]); - int err = lfs_testbd_createcfg(&cfg, test_disk, &bdcfg); - if (err) { - fprintf(stderr, "error: " - "could not create block device: %d\n", err); - exit(-1); + for (size_t j = 0; j < test_suites[i].case_count; j++) { + if (test_ids[t].case_ && strcmp( + test_suites[i].cases[j].name, test_ids[t].case_) != 0) { + continue; } - // run the test - printf("running %s#%zu\n", test_suites[i].cases[j].id, perm); - - test_suites[i].cases[j].run(&cfg); - - printf("finished %s#%zu\n", test_suites[i].cases[j].id, perm); - - // cleanup - err = lfs_testbd_destroy(&cfg); - if (err) { - fprintf(stderr, "error: " - "could not destroy block device: %d\n", err); - exit(-1); - } + run_perms(&test_suites[i], &test_suites[i].cases[j], + test_ids[t].perm, + test_ids[t].cycles, + test_ids[t].cycle_count); } } } @@ -521,48 +995,47 @@ static void run(void) { - // option handling enum opt_flags { - OPT_HELP = 'h', - OPT_SUMMARY = 'Y', - OPT_LIST_SUITES = 'l', - OPT_LIST_CASES = 'L', - OPT_LIST_PATHS = 1, - OPT_LIST_DEFINES = 2, - OPT_LIST_GEOMETRIES = 3, - OPT_LIST_DEFAULTS = 4, - OPT_DEFINE = 'D', - OPT_GEOMETRY = 'G', - OPT_NORMAL = 'n', - OPT_REENTRANT = 'r', - OPT_START = 5, - OPT_STEP = 6, - OPT_STOP = 7, - OPT_DISK = 'd', - OPT_TRACE = 't', + OPT_HELP = 'h', + OPT_SUMMARY = 'Y', + OPT_LIST_SUITES = 'l', + OPT_LIST_CASES = 'L', + OPT_LIST_PATHS = 1, + OPT_LIST_DEFINES = 2, + OPT_LIST_GEOMETRIES = 3, + OPT_LIST_DEFAULTS = 4, + OPT_LIST_POWERLOSSES = 5, + OPT_DEFINE = 'D', + OPT_GEOMETRY = 'G', + OPT_POWERLOSS = 'p', + OPT_START = 6, + OPT_STEP = 7, + OPT_STOP = 8, + OPT_DISK = 'd', + OPT_TRACE = 't', }; -const char *short_opts = "hYlLD:G:nrVp:t:"; +const char *short_opts = "hYlLD:G:p:nrVd:t:"; const struct option long_opts[] = { - {"help", no_argument, NULL, OPT_HELP}, - {"summary", no_argument, NULL, OPT_SUMMARY}, - {"list-suites", no_argument, NULL, OPT_LIST_SUITES}, - {"list-cases", no_argument, NULL, OPT_LIST_CASES}, - {"list-paths", no_argument, NULL, OPT_LIST_PATHS}, - {"list-defines", no_argument, NULL, OPT_LIST_DEFINES}, - {"list-geometries", no_argument, NULL, OPT_LIST_GEOMETRIES}, - {"list-defaults", no_argument, NULL, OPT_LIST_DEFAULTS}, - {"define", required_argument, NULL, OPT_DEFINE}, - {"geometry", required_argument, NULL, OPT_GEOMETRY}, - {"normal", no_argument, NULL, OPT_NORMAL}, - {"reentrant", no_argument, NULL, OPT_REENTRANT}, - {"start", required_argument, NULL, OPT_START}, - {"stop", required_argument, NULL, OPT_STOP}, - {"step", required_argument, NULL, OPT_STEP}, - {"disk", required_argument, NULL, OPT_DISK}, - {"trace", required_argument, NULL, OPT_TRACE}, + {"help", no_argument, NULL, OPT_HELP}, + {"summary", no_argument, NULL, OPT_SUMMARY}, + {"list-suites", no_argument, NULL, OPT_LIST_SUITES}, + {"list-cases", no_argument, NULL, OPT_LIST_CASES}, + {"list-paths", no_argument, NULL, OPT_LIST_PATHS}, + {"list-defines", no_argument, NULL, OPT_LIST_DEFINES}, + {"list-geometries", no_argument, NULL, OPT_LIST_GEOMETRIES}, + {"list-defaults", no_argument, NULL, OPT_LIST_DEFAULTS}, + {"list-powerlosses", no_argument, NULL, OPT_LIST_POWERLOSSES}, + {"define", required_argument, NULL, OPT_DEFINE}, + {"geometry", required_argument, NULL, OPT_GEOMETRY}, + {"powerloss", required_argument, NULL, OPT_POWERLOSS}, + {"start", required_argument, NULL, OPT_START}, + {"stop", required_argument, NULL, OPT_STOP}, + {"step", required_argument, NULL, OPT_STEP}, + {"disk", required_argument, NULL, OPT_DISK}, + {"trace", required_argument, NULL, OPT_TRACE}, {NULL, 0, NULL, 0}, }; @@ -575,24 +1048,27 @@ const char *const help_text[] = { "List the defines for each test permutation.", "List the disk geometries used for testing.", "List the default defines in this test-runner.", + "List the available power-loss scenarios.", "Override a test define.", "Filter by geometry.", - "Filter for normal tests. Can be combined.", - "Filter for reentrant tests. Can be combined.", + "Comma-separated list of power-loss scenarios to test. Defaults to 0,l.", "Start at the nth test.", "Stop before the nth test.", "Only run every n tests, calculated after --start and --stop.", - "Use this file as the disk.", + "Redirect block device operations to this file.", "Redirect trace output to this file.", }; int main(int argc, char **argv) { void (*op)(void) = run; - static const char **override_names = NULL; - static intmax_t *override_defines = NULL; - static size_t override_count = 0; - static size_t override_cap = 0; + const char **override_names = NULL; + intmax_t *override_defines = NULL; + size_t override_count = 0; + size_t override_capacity = 0; + + size_t test_powerloss_capacity = 0; + size_t test_id_capacity = 0; // parse options while (true) { @@ -676,6 +1152,9 @@ int main(int argc, char **argv) { case OPT_LIST_DEFAULTS: op = list_defaults; break; + case OPT_LIST_POWERLOSSES: + op = list_powerlosses; + break; // configuration case OPT_DEFINE: { // special case for -DGEOMETRY=, we treat this the same @@ -687,12 +1166,14 @@ int main(int argc, char **argv) { // realloc if necessary override_count += 1; - if (override_count > override_cap) { - override_cap = (2*override_cap > 4) ? 2*override_cap : 4; - override_names = realloc(override_names, override_cap - * sizeof(const char *)); - override_defines = realloc(override_defines, override_cap - * sizeof(intmax_t)); + if (override_count > override_capacity) { + override_capacity = (2*override_capacity > 4) + ? 2*override_capacity + : 4; + override_names = realloc(override_names, + override_capacity * sizeof(const char *)); + override_defines = realloc(override_defines, + override_capacity * sizeof(intmax_t)); } // parse into string key/intmax_t value, cannibalizing the @@ -719,12 +1200,134 @@ invalid_define: case OPT_GEOMETRY: test_geometry = optarg; break; - case OPT_NORMAL: - test_types |= TEST_NORMAL; - break; - case OPT_REENTRANT: - test_types |= TEST_REENTRANT; + case OPT_POWERLOSS: { + // reset our powerloss scenarios + if (test_powerloss_capacity > 0) { + free((test_powerloss_t*)test_powerlosses); + } + test_powerlosses = NULL; + test_powerloss_count = 0; + test_powerloss_capacity = 0; + + // parse the comma separated list of power-loss scenarios + while (*optarg) { + // allocate space + test_powerloss_count += 1; + if (test_powerloss_count > test_powerloss_capacity) { + test_powerloss_capacity + = (2*test_powerloss_capacity > 4) + ? 2*test_powerloss_capacity + : 4; + test_powerlosses = realloc( + (test_powerloss_t*)test_powerlosses, + test_powerloss_capacity + * sizeof(test_powerloss_t)); + } + + // parse the power-loss scenario + optarg += strspn(optarg, " "); + + // named power-loss scenario + size_t len = strcspn(optarg, " ,"); + for (size_t i = 0; builtin_powerlosses[i].long_name; i++) { + if ((len == 1 + && *optarg == builtin_powerlosses[i].short_name) + || (len == strlen( + builtin_powerlosses[i].long_name) + && memcmp(optarg, + builtin_powerlosses[i].long_name, + len) == 0)) { + ((test_powerloss_t*)test_powerlosses)[ + test_powerloss_count-1] + = builtin_powerlosses[i]; + optarg += len; + goto powerloss_next; + } + } + + // exhaustive permutations + // TODO + + // comma-separated permutation + if (*optarg == '{') { + // how many cycles? + size_t count = 1; + for (size_t i = 0; optarg[i]; i++) { + if (optarg[i] == ',') { + count += 1; + } + } + + // parse cycles + lfs_testbd_powercycles_t *cycles = malloc( + count * sizeof(lfs_testbd_powercycles_t)); + size_t i = 0; + char *s = optarg + 1; + while (true) { + char *parsed = NULL; + cycles[i] = strtoumax(s, &parsed, 0); + if (parsed == s) { + count -= 1; + i -= 1; + } + i += 1; + + s = parsed + strspn(parsed, " "); + if (*s == ',') { + s += 1; + continue; + } else if (*s == '}') { + s += 1; + break; + } else { + goto powerloss_unknown; + } + } + + ((test_powerloss_t*)test_powerlosses)[ + test_powerloss_count-1] = (test_powerloss_t){ + .run = run_powerloss_cycles, + .cycles = cycles, + .cycle_count = count, + }; + optarg = s; + goto powerloss_next; + } + + // leb16-encoded permutation + if (*optarg == '#') { + lfs_testbd_powercycles_t *cycles; + char *parsed = NULL; + size_t count = leb16_parse(optarg+1, &parsed, &cycles); + if (parsed == optarg+1) { + goto powerloss_unknown; + } + + ((test_powerloss_t*)test_powerlosses)[ + test_powerloss_count-1] = (test_powerloss_t){ + .run = run_powerloss_cycles, + .cycles = cycles, + .cycle_count = count, + }; + optarg = (char*)parsed; + goto powerloss_next; + } + +powerloss_unknown: + // unknown scenario? + fprintf(stderr, "error: " + "unknown power-loss scenario: %s\n", + optarg); + exit(-1); + +powerloss_next: + optarg += strcspn(optarg, ","); + if (*optarg == ',') { + optarg += 1; + } + } break; + } case OPT_START: { char *parsed = NULL; test_start = strtoumax(optarg, &parsed, 0); @@ -777,36 +1380,55 @@ invalid_define: } getopt_done: ; - // parse test identifier, if any, cannibalizing the arg in the process if (argc > optind) { - if (argc - optind > 1) { - fprintf(stderr, "error: more than one test identifier\n"); - exit(-1); - } + // reset our test identifier list + test_ids = NULL; + test_id_count = 0; + test_id_capacity = 0; + } + // parse test identifier, if any, cannibalizing the arg in the process + for (; argc > optind; optind++) { // parse suite char *suite = argv[optind]; char *case_ = strchr(suite, '#'); + size_t perm = -1; + lfs_testbd_powercycles_t *cycles = NULL; + size_t cycle_count = 0; if (case_) { *case_ = '\0'; case_ += 1; // parse case - char *perm = strchr(case_, '#'); - if (perm) { - *perm = '\0'; - perm += 1; + char *perm_ = strchr(case_, '#'); + if (perm_) { + *perm_ = '\0'; + perm_ += 1; + + // parse power cycles + char *cycles_ = strchr(perm_, '#'); + if (cycles_) { + *cycles_ = '\0'; + cycles_ += 1; + + char *parsed = NULL; + cycle_count = leb16_parse(cycles_, &parsed, &cycles); + if (parsed == cycles_) { + fprintf(stderr, "error: " + "could not parse test cycles: %s\n", cycles_); + exit(-1); + } + } char *parsed = NULL; - test_perm = strtoumax(perm, &parsed, 10); - if (parsed == perm) { - fprintf(stderr, "error: could not parse test identifier\n"); + perm = strtoumax(perm_, &parsed, 10); + if (parsed == perm_) { + fprintf(stderr, "error: " + "could not parse test permutation: %s\n", perm_); exit(-1); } } - - test_case = case_; } // remove optional path and .toml suffix @@ -820,7 +1442,22 @@ getopt_done: ; suite[suite_len-5] = '\0'; } - test_suite = suite; + // append to identifier list + test_id_count += 1; + if (test_id_count > test_id_capacity) { + test_id_capacity = (2*test_id_capacity > 4) + ? 2*test_id_capacity + : 4; + test_ids = realloc((test_id_t*)test_ids, + test_id_capacity * sizeof(test_id_t)); + } + ((test_id_t*)test_ids)[test_id_count-1] = (test_id_t){ + .suite = suite, + .case_ = case_, + .perm = perm, + .cycles = cycles, + .cycle_count = cycle_count, + }; } // register overrides @@ -832,4 +1469,16 @@ getopt_done: ; // cleanup (need to be done for valgrind testing) free(override_names); free(override_defines); + if (test_powerloss_capacity) { + for (size_t i = 0; i < test_powerloss_count; i++) { + free((lfs_testbd_powercycles_t*)test_powerlosses[i].cycles); + } + free((test_powerloss_t*)test_powerlosses); + } + if (test_id_capacity) { + for (size_t i = 0; i < test_id_count; i++) { + free((lfs_testbd_powercycles_t*)test_ids[i].cycles); + } + free((test_id_t*)test_ids); + } } diff --git a/runners/test_runner.h b/runners/test_runner.h index 64ad15d..27229ad 100644 --- a/runners/test_runner.h +++ b/runners/test_runner.h @@ -5,18 +5,16 @@ // generated test configurations -enum test_types { - TEST_NORMAL = 0x1, - TEST_REENTRANT = 0x2, +enum test_flags { + TEST_REENTRANT = 0x1, }; - -typedef uint8_t test_types_t; +typedef uint8_t test_flags_t; struct test_case { const char *id; const char *name; const char *path; - test_types_t types; + test_flags_t flags; size_t permutations; intmax_t (*const *const *defines)(void); @@ -29,7 +27,7 @@ struct test_suite { const char *id; const char *name; const char *path; - test_types_t types; + test_flags_t flags; const char *const *define_names; size_t define_count; @@ -54,6 +52,7 @@ intmax_t test_define(size_t define); #define ERASE_VALUE test_predefine(7) #define ERASE_CYCLES test_predefine(8) #define BADBLOCK_BEHAVIOR test_predefine(9) +#define POWERLOSS_BEHAVIOR test_predefine(10) #define TEST_PREDEFINE_NAMES { \ "READ_SIZE", \ @@ -66,17 +65,19 @@ intmax_t test_define(size_t define); "ERASE_VALUE", \ "ERASE_CYCLES", \ "BADBLOCK_BEHAVIOR", \ + "POWERLOSS_BEHAVIOR", \ } -#define TEST_PREDEFINE_COUNT 10 +#define TEST_PREDEFINE_COUNT 11 // default predefines #define TEST_DEFAULTS { \ - /* LOOKAHEAD_SIZE */ 16, \ - /* BLOCK_CYCLES */ -1, \ - /* ERASE_VALUE */ 0xff, \ - /* ERASE_CYCLES */ 0, \ - /* BADBLOCK_BEHAVIOR */ LFS_TESTBD_BADBLOCK_PROGERROR, \ + /* LOOKAHEAD_SIZE */ 16, \ + /* BLOCK_CYCLES */ -1, \ + /* ERASE_VALUE */ 0xff, \ + /* ERASE_CYCLES */ 0, \ + /* BADBLOCK_BEHAVIOR */ LFS_TESTBD_BADBLOCK_PROGERROR, \ + /* POWERLOSS_BEHAVIOR */ LFS_TESTBD_POWERLOSS_NOOP, \ } #define TEST_DEFAULT_DEFINE_COUNT 5 diff --git a/scripts/test.py b/scripts/test.py index 281265e..cbc7ab9 100755 --- a/scripts/test.py +++ b/scripts/test.py @@ -73,8 +73,6 @@ class TestCase: self.in_ = config.pop('in', config.pop('suite_in', None)) - self.normal = config.pop('normal', - config.pop('suite_normal', True)) self.reentrant = config.pop('reentrant', config.pop('suite_reentrant', False)) @@ -159,7 +157,6 @@ class TestSuite: # a couple of these we just forward to all cases defines = config.pop('defines', {}) in_ = config.pop('in', None) - normal = config.pop('normal', True) reentrant = config.pop('reentrant', False) self.cases = [] @@ -172,7 +169,6 @@ class TestSuite: 'suite': self.name, 'suite_defines': defines, 'suite_in': in_, - 'suite_normal': normal, 'suite_reentrant': reentrant, **case})) @@ -181,7 +177,6 @@ class TestSuite: set(case.defines) for case in self.cases)) # combine other per-case things - self.normal = any(case.normal for case in self.cases) self.reentrant = any(case.reentrant for case in self.cases) for k in config.keys(): @@ -236,6 +231,12 @@ def compile(**args): f.write = write f.writeln = writeln + f.writeln("// Generated by %s:" % sys.argv[0]) + f.writeln("//") + f.writeln("// %s" % ' '.join(sys.argv)) + f.writeln("//") + f.writeln() + # redirect littlefs tracing f.writeln('#define LFS_TRACE_(fmt, ...) do { \\') f.writeln(8*' '+'extern FILE *test_trace; \\') @@ -366,10 +367,10 @@ def compile(**args): f.writeln(4*' '+'.id = "%s",' % suite.id()) f.writeln(4*' '+'.name = "%s",' % suite.name) f.writeln(4*' '+'.path = "%s",' % suite.path) - f.writeln(4*' '+'.types = %s,' - % ' | '.join(filter(None, [ - 'TEST_NORMAL' if suite.normal else None, - 'TEST_REENTRANT' if suite.reentrant else None]))) + f.writeln(4*' '+'.flags = %s,' + % (' | '.join(filter(None, [ + 'TEST_REENTRANT' if suite.reentrant else None])) + or 0)) if suite.defines: # create suite define names f.writeln(4*' '+'.define_names = (const char *const[]){') @@ -384,10 +385,10 @@ def compile(**args): f.writeln(12*' '+'.id = "%s",' % case.id()) f.writeln(12*' '+'.name = "%s",' % case.name) f.writeln(12*' '+'.path = "%s",' % case.path) - f.writeln(12*' '+'.types = %s,' - % ' | '.join(filter(None, [ - 'TEST_NORMAL' if case.normal else None, - 'TEST_REENTRANT' if case.reentrant else None]))) + f.writeln(12*' '+'.flags = %s,' + % (' | '.join(filter(None, [ + 'TEST_REENTRANT' if case.reentrant else None])) + or 0)) f.writeln(12*' '+'.permutations = %d,' % len(case.permutations)) if case.defines: @@ -461,12 +462,13 @@ def runner(**args): '--error-exitcode=4', '-q']) - # filter tests? - if args.get('normal'): cmd.append('-n') - if args.get('reentrant'): cmd.append('-r') + # other context if args.get('geometry'): cmd.append('-G%s' % args.get('geometry')) + if args.get('powerloss'): + cmd.append('-p%s' % args.get('powerloss')) + # defines? if args.get('define'): for define in args.get('define'): @@ -476,12 +478,13 @@ def runner(**args): def list_(**args): cmd = runner(**args) - if args.get('summary'): cmd.append('--summary') - if args.get('list_suites'): cmd.append('--list-suites') - if args.get('list_cases'): cmd.append('--list-cases') - if args.get('list_paths'): cmd.append('--list-paths') - if args.get('list_defines'): cmd.append('--list-defines') - if args.get('list_geometries'): cmd.append('--list-geometries') + if args.get('summary'): cmd.append('--summary') + if args.get('list_suites'): cmd.append('--list-suites') + if args.get('list_cases'): cmd.append('--list-cases') + if args.get('list_paths'): cmd.append('--list-paths') + if args.get('list_defines'): cmd.append('--list-defines') + if args.get('list_geometries'): cmd.append('--list-geometries') + if args.get('list_powerlosses'): cmd.append('--list-powerlosses') if args.get('verbose'): print(' '.join(shlex.quote(c) for c in cmd)) @@ -598,11 +601,12 @@ def run_stage(name, runner_, **args): passed_suite_perms = co.defaultdict(lambda: 0) passed_case_perms = co.defaultdict(lambda: 0) passed_perms = 0 + powerlosses = 0 failures = [] killed = False pattern = re.compile('^(?:' - '(?Prunning|finished|skipped) ' + '(?Prunning|finished|skipped|powerloss) ' '(?P(?P(?P[^#]+)#[^\s#]+)[^\s]*)' '|' '(?P[^:]+):(?P\d+):(?Passert):' ' *(?P.*)' ')$') @@ -613,6 +617,7 @@ def run_stage(name, runner_, **args): nonlocal passed_suite_perms nonlocal passed_case_perms nonlocal passed_perms + nonlocal powerlosses nonlocal locals # run the tests! @@ -659,6 +664,9 @@ def run_stage(name, runner_, **args): last_id = m.group('id') last_output = [] last_assert = None + elif op == 'powerloss': + last_id = m.group('id') + powerlosses += 1 elif op == 'finished': passed_suite_perms[m.group('suite')] += 1 passed_case_perms[m.group('case')] += 1 @@ -766,6 +774,8 @@ def run_stage(name, runner_, **args): len(expected_case_perms)) if not args.get('by_cases') else None, '%d/%d perms' % (passed_perms, expected_perms), + '%dpls!' % powerlosses + if powerlosses else None, '\x1b[31m%d/%d failures\x1b[m' % (len(failures), expected_perms) if failures else None])))) @@ -785,6 +795,7 @@ def run_stage(name, runner_, **args): return ( expected_perms, passed_perms, + powerlosses, failures, killed) @@ -806,33 +817,34 @@ def run(**args): expected = 0 passed = 0 + powerlosses = 0 failures = [] - for type, by in it.product( - ['normal', 'reentrant'], - expected_case_perms.keys() if args.get('by_cases') - else expected_suite_perms.keys() if args.get('by_suites') - else [None]): + for by in (expected_case_perms.keys() if args.get('by_cases') + else expected_suite_perms.keys() if args.get('by_suites') + else [None]): # rebuild runner for each stage to override test identifier if needed stage_runner = runner(**args | { - 'test_ids': [by] if by is not None else args.get('test_ids', []), - 'normal': type == 'normal', - 'reentrant': type == 'reentrant'}) + 'test_ids': [by] if by is not None else args.get('test_ids', [])}) # spawn jobs for stage - expected_, passed_, failures_, killed = run_stage( - '%s %s' % (type, by or 'tests'), stage_runner, **args) + expected_, passed_, powerlosses_, failures_, killed = run_stage( + by or 'tests', stage_runner, **args) expected += expected_ passed += passed_ + powerlosses += powerlosses_ failures.extend(failures_) if (failures and not args.get('keep_going')) or killed: break # show summary print() - print('\x1b[%dmdone:\x1b[m %d/%d passed, %d/%d failed, in %.2fs' + print('\x1b[%dmdone:\x1b[m %s' # %d/%d passed, %d/%d failed%s, in %.2fs' % (32 if not failures else 31, - passed, expected, len(failures), expected, - time.time()-start)) + ', '.join(filter(None, [ + '%d/%d passed' % (passed, expected), + '%d/%d failed' % (len(failures), expected), + '%dpls!' % powerlosses if powerlosses else None, + 'in %.2fs' % (time.time()-start)])))) print() # print each failure @@ -844,7 +856,7 @@ def run(**args): for failure in failures: # show summary of failure path, lineno = runner_paths[testcase(failure.id)] - defines = runner_defines[failure.id] + defines = runner_defines.get(failure.id, {}) print('\x1b[01m%s:%d:\x1b[01;31mfailure:\x1b[m %s%s failed' % (path, lineno, failure.id, @@ -913,8 +925,9 @@ def main(**args): or args.get('list_cases') or args.get('list_paths') or args.get('list_defines') + or args.get('list_defaults') or args.get('list_geometries') - or args.get('list_defaults')): + or args.get('list_powerlosses')): list_(**args) else: run(**args) @@ -930,7 +943,7 @@ if __name__ == "__main__": help="Description of testis to run. May be a directory, path, or \ test identifier. Test identifiers are of the form \ ##, but suffixes can be \ - dropped to run any matching tests. Defaults to %r." % TEST_PATHS) + dropped to run any matching tests. Defaults to %s." % TEST_PATHS) parser.add_argument('-v', '--verbose', action='store_true', help="Output commands that run behind the scenes.") # test flags @@ -945,20 +958,21 @@ if __name__ == "__main__": help="List the path for each test case.") test_parser.add_argument('--list-defines', action='store_true', help="List the defines for each test permutation.") - test_parser.add_argument('--list-geometries', action='store_true', - help="List the disk geometries used for testing.") test_parser.add_argument('--list-defaults', action='store_true', help="List the default defines in this test-runner.") + test_parser.add_argument('--list-geometries', action='store_true', + help="List the disk geometries used for testing.") + test_parser.add_argument('--list-powerlosses', action='store_true', + help="List the available power-loss scenarios.") test_parser.add_argument('-D', '--define', action='append', help="Override a test define.") test_parser.add_argument('-G', '--geometry', help="Filter by geometry.") - test_parser.add_argument('-n', '--normal', action='store_true', - help="Filter for normal tests. Can be combined.") - test_parser.add_argument('-r', '--reentrant', action='store_true', - help="Filter for reentrant tests. Can be combined.") + test_parser.add_argument('-p', '--powerloss', + help="Comma-separated list of power-loss scenarios to test. \ + Defaults to 0,l.") test_parser.add_argument('-d', '--disk', - help="Use this file as the disk.") + help="Redirect block device operations to this file.") test_parser.add_argument('-t', '--trace', help="Redirect trace output to this file.") test_parser.add_argument('-o', '--output',