Browse Source

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.
Christopher Haster 3 years ago
parent
commit
61455b6191
8 changed files with 1146 additions and 501 deletions
  1. 1 1
      Makefile
  2. 272 176
      bd/lfs_testbd.c
  3. 90 29
      bd/lfs_testbd.h
  4. 0 2
      lfs.h
  5. 1 0
      lfs_util.h
  6. 708 234
      runners/test_runner.c
  7. 14 13
      runners/test_runner.h
  8. 60 46
      scripts/test.py

+ 1 - 1
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

+ 272 - 176
bd/lfs_testbd.c

@@ -11,6 +11,79 @@
 #include <stdlib.h>
 
 
+// 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;
 
-    // setup testing things
-    bd->persist = path;
-    bd->power_cycles = bd->cfg->power_cycles;
-
-    // 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;
-            }
-        }
+    // 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*));
 
-    // 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);
-    }
+    // setup testing things
+    bd->power_cycles = bd->cfg->power_cycles;
+    bd->branches = NULL;
+    bd->branch_capacity = 0;
+    bd->branch_count = 0;
 
-    // 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);
 
-/// 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);
-    }
+    LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", 0);
+    return 0;
 }
 
-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);
-    }
-}
+///// 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);
+//    }
+//}
+
+
+
+// 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;
+}

+ 90 - 29
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" */

+ 0 - 2
lfs.h

@@ -8,8 +8,6 @@
 #ifndef LFS_H
 #define LFS_H
 
-#include <stdint.h>
-#include <stdbool.h>
 #include "lfs_util.h"
 
 #ifdef __cplusplus

+ 1 - 0
lfs_util.h

@@ -23,6 +23,7 @@
 // System includes
 #include <stdint.h>
 #include <stdbool.h>
+#include <sys/types.h>
 #include <string.h>
 #include <inttypes.h>
 

File diff suppressed because it is too large
+ 708 - 234
runners/test_runner.c


+ 14 - 13
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
 

+ 60 - 46
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('^(?:'
-            '(?P<op>running|finished|skipped) '
+            '(?P<op>running|finished|skipped|powerloss) '
                 '(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)'
             '|' '(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
                 ' *(?P<message>.*)' ')$')
@@ -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 \
             <suite_name>#<case_name>#<permutation>, 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',

Some files were not shown because too many files changed in this diff