diff --git a/Makefile b/Makefile index 17d3ab3..67e1c9e 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ CC ?= gcc AR ?= ar SIZE ?= size -SRC += $(wildcard *.c rambd/*.c filebd/*.c) +SRC += $(wildcard *.c rambd/*.c filebd/*.c testbd/*.c) OBJ := $(SRC:.c=.o) DEP := $(SRC:.c=.d) ASM := $(SRC:.c=.s) diff --git a/filebd/lfs_filebd.c b/filebd/lfs_filebd.c index d7fd89f..a897a02 100644 --- a/filebd/lfs_filebd.c +++ b/filebd/lfs_filebd.c @@ -11,7 +11,7 @@ #include int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, - const struct lfs_filebd_config *filecfg) { + const struct lfs_filebd_config *bdcfg) { LFS_TRACE("lfs_filebd_createcfg(%p {.context=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " @@ -22,9 +22,9 @@ int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, (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*)filecfg, filecfg->erase_value); + path, (void*)bdcfg, bdcfg->erase_value); lfs_filebd_t *bd = cfg->context; - bd->cfg = filecfg; + bd->cfg = bdcfg; // open file bd->fd = open(path, O_RDWR | O_CREAT, 0666); @@ -55,11 +55,17 @@ int lfs_filebd_create(const struct lfs_config *cfg, const char *path) { return err; } -void lfs_filebd_destroy(const struct lfs_config *cfg) { +int lfs_filebd_destroy(const struct lfs_config *cfg) { LFS_TRACE("lfs_filebd_destroy(%p)", (void*)cfg); lfs_filebd_t *bd = cfg->context; - close(bd->fd); - LFS_TRACE("lfs_filebd_destroy -> %s", "void"); + int err = close(bd->fd); + if (err < 0) { + err = -errno; + LFS_TRACE("lfs_filebd_destroy -> %d", err); + return err; + } + LFS_TRACE("lfs_filebd_destroy -> %d", 0); + return 0; } int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, diff --git a/filebd/lfs_filebd.h b/filebd/lfs_filebd.h index ae28858..271e873 100644 --- a/filebd/lfs_filebd.h +++ b/filebd/lfs_filebd.h @@ -17,8 +17,9 @@ extern "C" // filebd config (optional) struct lfs_filebd_config { - // 8-bit erase value to simulate erasing with. -1 indicates no erase - // occurs, which is still a valid block device + // 8-bit erase value to use for simulating erases. -1 does not simulate + // erases, which can speed up testing by avoiding all the extra block-device + // operations to store the erase value. int32_t erase_value; }; @@ -32,10 +33,10 @@ typedef struct lfs_filebd { // Create a file block device using the geometry in lfs_config int lfs_filebd_create(const struct lfs_config *cfg, const char *path); int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, - const struct lfs_filebd_config *ramcfg); + const struct lfs_filebd_config *bdcfg); // Clean up memory associated with block device -void lfs_filebd_destroy(const struct lfs_config *cfg); +int lfs_filebd_destroy(const struct lfs_config *cfg); // Read a block int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, diff --git a/lfs.c b/lfs.c index 4553a6e..2758c29 100644 --- a/lfs.c +++ b/lfs.c @@ -1651,9 +1651,11 @@ relocate: // relocate half of pair int err = lfs_alloc(lfs, &dir->pair[1]); - if (err && (err != LFS_ERR_NOSPC && !exhausted)) { + if (err && (err != LFS_ERR_NOSPC || !exhausted)) { return err; } + + exhausted = false; continue; } diff --git a/rambd/lfs_rambd.c b/rambd/lfs_rambd.c index 5bf8cc1..ce06556 100644 --- a/rambd/lfs_rambd.c +++ b/rambd/lfs_rambd.c @@ -7,7 +7,7 @@ #include "rambd/lfs_rambd.h" int lfs_rambd_createcfg(const struct lfs_config *cfg, - const struct lfs_rambd_config *ramcfg) { + const struct lfs_rambd_config *bdcfg) { LFS_TRACE("lfs_rambd_createcfg(%p {.context=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " @@ -17,9 +17,9 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg, (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, - (void*)ramcfg, ramcfg->erase_value, ramcfg->buffer); + (void*)bdcfg, bdcfg->erase_value, bdcfg->buffer); lfs_rambd_t *bd = cfg->context; - bd->cfg = ramcfg; + bd->cfg = bdcfg; // allocate buffer? if (bd->cfg->buffer) { @@ -57,14 +57,15 @@ int lfs_rambd_create(const struct lfs_config *cfg) { return err; } -void lfs_rambd_destroy(const struct lfs_config *cfg) { +int lfs_rambd_destroy(const struct lfs_config *cfg) { LFS_TRACE("lfs_rambd_destroy(%p)", (void*)cfg); // clean up memory lfs_rambd_t *bd = cfg->context; if (!bd->cfg->buffer) { lfs_free(bd->buffer); } - LFS_TRACE("lfs_rambd_destroy -> %s", "void"); + LFS_TRACE("lfs_rambd_destroy -> %d", 0); + return 0; } int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, diff --git a/rambd/lfs_rambd.h b/rambd/lfs_rambd.h index a4b9515..139ddd6 100644 --- a/rambd/lfs_rambd.h +++ b/rambd/lfs_rambd.h @@ -35,10 +35,10 @@ typedef struct lfs_rambd { // Create a RAM block device using the geometry in lfs_config int lfs_rambd_create(const struct lfs_config *cfg); int lfs_rambd_createcfg(const struct lfs_config *cfg, - const struct lfs_rambd_config *ramcfg); + const struct lfs_rambd_config *bdcfg); // Clean up memory associated with block device -void lfs_rambd_destroy(const struct lfs_config *cfg); +int lfs_rambd_destroy(const struct lfs_config *cfg); // Read a block int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, diff --git a/scripts/explode_asserts.py b/scripts/explode_asserts.py index 7c24c63..ff3f260 100755 --- a/scripts/explode_asserts.py +++ b/scripts/explode_asserts.py @@ -146,7 +146,7 @@ def pnested(): pexpr = ( # shortcut for a bit better performance - p.regex('[^%s/#\'"();{}=><,&|-]+' % ASSERT_CHARS) | + p.regex('[^%s/#\'"():;{}=><,&|-]+' % ASSERT_CHARS) | pws | passert | pstring | @@ -157,7 +157,7 @@ pexpr = ( @p.generate def pstmt(): ws = yield pws.many() - lh = yield pexpr.until(p.string('=>') | p.regex('[;{}]')) + lh = yield pexpr.until(p.string('=>') | p.regex('[:;{}]')) op = yield p.string('=>').optional() if op == '=>': rh = yield pstmt @@ -168,7 +168,7 @@ def pstmt(): @p.generate def pstmts(): a = yield pstmt - b = yield (p.regex('[;{}]') + pstmt).many() + b = yield (p.regex('[:;{}]') + pstmt).many() return [a] + b def main(args): diff --git a/scripts/test_.py b/scripts/test_.py index 2b78510..c4e44a1 100755 --- a/scripts/test_.py +++ b/scripts/test_.py @@ -19,7 +19,7 @@ # x config chaining correct # - why can't gdb see my defines? # - say no to internal? -# - buffering stdout issues? +# x buffering stdout issues? import toml import glob @@ -33,6 +33,9 @@ import base64 import sys import copy import shlex +import pty +import errno +import signal TESTDIR = 'tests_' RULES = """ @@ -45,98 +48,31 @@ $(foreach target,$(SRC),$(eval $(FLATTEN))) -include tests_/*.d .SECONDARY: -%.test: override CFLAGS += -fdiagnostics-color=always -%.test: override CFLAGS += -ggdb +%.test: override CFLAGS += -gdwarf-2 +%.test: override CFLAGS += -ggdb3 +%.test: override CFLAGS += -g3 %.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f) $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ """ GLOBALS = """ //////////////// AUTOGENERATED TEST //////////////// #include "lfs.h" -#include "filebd/lfs_filebd.h" -#include "rambd/lfs_rambd.h" +#include "testbd/lfs_testbd.h" #include - -extern const char *lfs_testbd_disk; -typedef union { - lfs_filebd_t filebd; - lfs_rambd_t rambd; -} lfs_testbd_t; -struct lfs_testbd_config { - struct lfs_filebd_config filecfg; - struct lfs_rambd_config ramcfg; -}; - -__attribute__((unused)) -static int lfs_testbd_createcfg(const struct lfs_config *cfg, - const struct lfs_testbd_config *bdcfg) { - if (lfs_testbd_disk) { - return lfs_filebd_createcfg(cfg, lfs_testbd_disk, &bdcfg->filecfg); - } else { - return lfs_rambd_createcfg(cfg, &bdcfg->ramcfg); - } -} - -__attribute__((unused)) -static void lfs_testbd_destroy(const struct lfs_config *cfg) { - if (lfs_testbd_disk) { - lfs_filebd_destroy(cfg); - } else { - lfs_rambd_destroy(cfg); - } -} - -__attribute__((unused)) -static int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, - lfs_off_t off, void *buffer, lfs_size_t size) { - if (lfs_testbd_disk) { - return lfs_filebd_read(cfg, block, off, buffer, size); - } else { - return lfs_rambd_read(cfg, block, off, buffer, size); - } -} - -__attribute__((unused)) -static int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, - lfs_off_t off, const void *buffer, lfs_size_t size) { - if (lfs_testbd_disk) { - return lfs_filebd_prog(cfg, block, off, buffer, size); - } else { - return lfs_rambd_prog(cfg, block, off, buffer, size); - } -} - -__attribute__((unused)) -static int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { - if (lfs_testbd_disk) { - return lfs_filebd_erase(cfg, block); - } else { - return lfs_rambd_erase(cfg, block); - } -} - -__attribute__((unused)) -static int lfs_testbd_sync(const struct lfs_config *cfg) { - if (lfs_testbd_disk) { - return lfs_filebd_sync(cfg); - } else { - return lfs_rambd_sync(cfg); - } -} +extern const char *lfs_testbd_path; +extern uint32_t lfs_testbd_cycles; """ DEFINES = { - "LFS_BD_READ": "lfs_testbd_read", - "LFS_BD_PROG": "lfs_testbd_prog", - "LFS_BD_ERASE": "lfs_testbd_erase", - "LFS_BD_SYNC": "lfs_testbd_sync", - "LFS_READ_SIZE": 16, - "LFS_PROG_SIZE": "LFS_READ_SIZE", - "LFS_BLOCK_SIZE": 512, - "LFS_BLOCK_COUNT": 1024, - "LFS_BLOCK_CYCLES": -1, - "LFS_CACHE_SIZE": "(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)", - "LFS_LOOKAHEAD_SIZE": 16, - "LFS_ERASE_VALUE": 0xff, + 'LFS_READ_SIZE': 16, + 'LFS_PROG_SIZE': 'LFS_READ_SIZE', + 'LFS_BLOCK_SIZE': 512, + 'LFS_BLOCK_COUNT': 1024, + 'LFS_BLOCK_CYCLES': -1, + 'LFS_CACHE_SIZE': '(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)', + 'LFS_LOOKAHEAD_SIZE': 16, + 'LFS_ERASE_VALUE': 0xff, + 'LFS_ERASE_CYCLES': 0, + 'LFS_BADBLOCK_BEHAVIOR': 'LFS_TESTBD_BADBLOCK_NOPROG', } PROLOGUE = """ // prologue @@ -152,10 +88,10 @@ PROLOGUE = """ __attribute__((unused)) const struct lfs_config cfg = { .context = &bd, - .read = LFS_BD_READ, - .prog = LFS_BD_PROG, - .erase = LFS_BD_ERASE, - .sync = LFS_BD_SYNC, + .read = lfs_testbd_read, + .prog = lfs_testbd_prog, + .erase = lfs_testbd_erase, + .sync = lfs_testbd_sync, .read_size = LFS_READ_SIZE, .prog_size = LFS_PROG_SIZE, .block_size = LFS_BLOCK_SIZE, @@ -166,15 +102,17 @@ PROLOGUE = """ }; __attribute__((unused)) const struct lfs_testbd_config bdcfg = { - .filecfg.erase_value = LFS_ERASE_VALUE, - .ramcfg.erase_value = LFS_ERASE_VALUE, + .erase_value = LFS_ERASE_VALUE, + .erase_cycles = LFS_ERASE_CYCLES, + .badblock_behavior = LFS_BADBLOCK_BEHAVIOR, + .power_cycles = lfs_testbd_cycles, }; - lfs_testbd_createcfg(&cfg, &bdcfg) => 0; + lfs_testbd_createcfg(&cfg, lfs_testbd_path, &bdcfg) => 0; """ EPILOGUE = """ // epilogue - lfs_testbd_destroy(&cfg); + lfs_testbd_destroy(&cfg) => 0; """ PASS = '\033[32m✓\033[0m' FAIL = '\033[31m✗\033[0m' @@ -224,15 +162,15 @@ class TestCase: def build(self, f, **_): # prologue - f.write('void test_case%d(%s) {\n' % (self.caseno, ','.join( + for k, v in sorted(self.defines.items()): + if k not in self.suite.defines: + f.write('#define %s %s\n' % (k, v)) + + f.write('void test_case%d(%s) {' % (self.caseno, ','.join( '\n'+8*' '+'__attribute__((unused)) intmax_t %s' % k for k in sorted(self.perms[0].defines) if k not in self.defines))) - for k, v in sorted(self.defines.items()): - if k not in self.suite.defines: - f.write(4*' '+'#define %s %s\n' % (k, v)) - f.write(PROLOGUE) f.write('\n') f.write(4*' '+'// test case %d\n' % self.caseno) @@ -243,13 +181,11 @@ class TestCase: # epilogue f.write(EPILOGUE) - f.write('\n') + f.write('}\n') for k, v in sorted(self.defines.items()): if k not in self.suite.defines: - f.write(4*' '+'#undef %s\n' % k) - - f.write('}\n') + f.write('#undef %s\n' % k) def shouldtest(self, **args): if (self.filter is not None and @@ -265,7 +201,8 @@ class TestCase: else: return True - def test(self, exec=[], persist=False, gdb=False, failure=None, **args): + def test(self, exec=[], persist=False, cycles=None, + gdb=False, failure=None, **args): # build command cmd = exec + ['./%s.test' % self.suite.path, repr(self.caseno), repr(self.permno)] @@ -280,6 +217,10 @@ class TestCase: cmd.append(self.suite.path + '.disk') + # simulate power-loss after n cycles? + if cycles: + cmd.append(str(cycles)) + # failed? drop into debugger? if gdb and failure: ncmd = ['gdb'] @@ -295,19 +236,25 @@ class TestCase: if args.get('verbose', False): print(' '.join(shlex.quote(c) for c in ncmd)) + signal.signal(signal.SIGINT, signal.SIG_IGN) sys.exit(sp.call(ncmd)) # run test case! - stdout = [] - assert_ = None + mpty, spty = pty.openpty() if args.get('verbose', False): print(' '.join(shlex.quote(c) for c in cmd)) - proc = sp.Popen(cmd, - universal_newlines=True, - bufsize=1, - stdout=sp.PIPE, - stderr=sp.STDOUT) - for line in iter(proc.stdout.readline, ''): + proc = sp.Popen(cmd, stdout=spty, stderr=spty) + os.close(spty) + mpty = os.fdopen(mpty, 'r', 1) + stdout = [] + assert_ = None + while True: + try: + line = mpty.readline() + except OSError as e: + if e.errno == errno.EIO: + break + raise stdout.append(line) if args.get('verbose', False): sys.stdout.write(line) @@ -361,36 +308,23 @@ class ReentrantTestCase(TestCase): return self.reentrant and super().shouldtest(**args) def test(self, exec=[], persist=False, gdb=False, failure=None, **args): - # clear disk first? - if persist != 'noerase': - try: - os.remove(self.suite.path + '.disk') - except FileNotFoundError: - pass - for cycles in it.count(1): + # clear disk first? + if cycles == 1 and persist != 'noerase': + persist = 'erase' + else: + persist = 'noerase' + # exact cycle we should drop into debugger? if gdb and failure and failure.cycleno == cycles: - return super().test(exec=exec, persist='noerase', - gdb=gdb, failure=failure, **args) + return super().test(gdb=gdb, + persist=persist, failure=failure, **args) # run tests, but kill the program after prog/erase has # been hit n cycles. We exit with a special return code if the # program has not finished, since this isn't a test failure. - nexec = exec + [ - 'gdb', '-batch-silent', - '-ex', 'handle all nostop', - '-ex', 'b lfs_filebd_prog', - '-ex', 'b lfs_filebd_erase', - '-ex', 'r', - ] + cycles*['-ex', 'c'] + [ - '-ex', 'q ' - '!$_isvoid($_exitsignal) ? $_exitsignal : ' - '!$_isvoid($_exitcode) ? $_exitcode : ' - '33', - '--args'] try: - return super().test(exec=nexec, persist='noerase', **args) + return super().test(persist=persist, cycles=cycles, **args) except TestFailure as nfailure: if nfailure.returncode == 33: continue @@ -535,11 +469,13 @@ class TestSuite: case.build(tfs[case.in_], **args) tf.write('\n') - tf.write('const char *lfs_testbd_disk;\n') + tf.write('const char *lfs_testbd_path;\n') + tf.write('uint32_t lfs_testbd_cycles;\n') tf.write('int main(int argc, char **argv) {\n') - tf.write(4*' '+'int case_ = (argc >= 2) ? atoi(argv[1]) : 0;\n') - tf.write(4*' '+'int perm = (argc >= 3) ? atoi(argv[2]) : 0;\n') - tf.write(4*' '+'lfs_testbd_disk = (argc >= 4) ? argv[3] : NULL;\n') + tf.write(4*' '+'int case_ = (argc > 1) ? atoi(argv[1]) : 0;\n') + tf.write(4*' '+'int perm = (argc > 2) ? atoi(argv[2]) : 0;\n') + tf.write(4*' '+'lfs_testbd_path = (argc > 3) ? argv[3] : NULL;\n') + tf.write(4*' '+'lfs_testbd_cycles = (argc > 4) ? atoi(argv[4]) : 0;\n') for perm in self.perms: # test declaration tf.write(4*' '+'extern void test_case%d(%s);\n' % ( @@ -671,15 +607,20 @@ def main(**args): cmd = (['make', '-f', 'Makefile'] + list(it.chain.from_iterable(['-f', m] for m in makefiles)) + [target for target in targets]) - stdout = [] + mpty, spty = pty.openpty() if args.get('verbose', False): print(' '.join(shlex.quote(c) for c in cmd)) - proc = sp.Popen(cmd, - universal_newlines=True, - bufsize=1, - stdout=sp.PIPE, - stderr=sp.STDOUT) - for line in iter(proc.stdout.readline, ''): + proc = sp.Popen(cmd, stdout=spty, stderr=spty) + os.close(spty) + mpty = os.fdopen(mpty, 'r', 1) + stdout = [] + while True: + try: + line = mpty.readline() + except OSError as e: + if e.errno == errno.EIO: + break + raise stdout.append(line) if args.get('verbose', False): sys.stdout.write(line) diff --git a/testbd/lfs_testbd.c b/testbd/lfs_testbd.c new file mode 100644 index 0000000..f13140e --- /dev/null +++ b/testbd/lfs_testbd.c @@ -0,0 +1,289 @@ +/* + * Testing block device, wraps filebd and rambd while providing a bunch + * of hooks for testing littlefs in various conditions. + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "testbd/lfs_testbd.h" + +#include + + +int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, + const struct lfs_testbd_config *bdcfg) { + LFS_TRACE("lfs_testbd_createcfg(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " + "\"%s\", " + "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", " + ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", " + ".buffer=%p, .wear_buffer=%p})", + (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); + lfs_testbd_t *bd = cfg->context; + bd->cfg = bdcfg; + + // setup testing things + bd->persist = path; + bd->power_cycles = bd->cfg->power_cycles; + + 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_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) { + bd->u.file.cfg = (struct lfs_filebd_config){ + .erase_value = bd->cfg->erase_value, + }; + int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg); + LFS_TRACE("lfs_testbd_createcfg -> %d", err); + return err; + } else { + bd->u.ram.cfg = (struct lfs_rambd_config){ + .erase_value = bd->cfg->erase_value, + .buffer = bd->cfg->buffer, + }; + int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg); + LFS_TRACE("lfs_testbd_createcfg -> %d", err); + return err; + } +} + +int lfs_testbd_create(const struct lfs_config *cfg, const char *path) { + LFS_TRACE("lfs_testbd_create(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " + "\"%s\")", + (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); + static const struct lfs_testbd_config defaults = {.erase_value=-1}; + int err = lfs_testbd_createcfg(cfg, path, &defaults); + LFS_TRACE("lfs_testbd_create -> %d", err); + return err; +} + +int lfs_testbd_destroy(const struct lfs_config *cfg) { + LFS_TRACE("lfs_testbd_destroy(%p)", (void*)cfg); + lfs_testbd_t *bd = cfg->context; + if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) { + lfs_free(bd->wear); + } + + if (bd->persist) { + int err = lfs_filebd_destroy(cfg); + LFS_TRACE("lfs_testbd_destroy -> %d", err); + return err; + } else { + int err = lfs_rambd_destroy(cfg); + LFS_TRACE("lfs_testbd_destroy -> %d", err); + return err; + } +} + +/// 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 /// +int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_testbd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", + (void*)cfg, block, off, buffer, size); + lfs_testbd_t *bd = cfg->context; + + // check if read is valid + LFS_ASSERT(off % cfg->read_size == 0); + LFS_ASSERT(size % cfg->read_size == 0); + LFS_ASSERT(block < cfg->block_count); + + // block bad? + if (bd->cfg->erase_cycles && + bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOREAD && + bd->wear[block] >= bd->cfg->erase_cycles) { + LFS_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + } + + // read + int err = lfs_testbd_rawread(cfg, block, off, buffer, size); + LFS_TRACE("lfs_testbd_read -> %d", err); + return err; +} + +int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_testbd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", + (void*)cfg, block, off, buffer, size); + lfs_testbd_t *bd = cfg->context; + + // check if write is valid + LFS_ASSERT(off % cfg->prog_size == 0); + LFS_ASSERT(size % cfg->prog_size == 0); + LFS_ASSERT(block < cfg->block_count); + + // block bad? + if (bd->cfg->erase_cycles && + bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOPROG && + bd->wear[block] >= bd->cfg->erase_cycles) { + LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + } + + // prog + int err = lfs_testbd_rawprog(cfg, block, off, buffer, size); + if (err) { + LFS_TRACE("lfs_testbd_prog -> %d", err); + return err; + } + + // 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_testbd_rawsync(cfg); + // simulate power loss + exit(33); + } + } + + LFS_TRACE("lfs_testbd_prog -> %d", 0); + return 0; +} + +int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { + LFS_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); + lfs_testbd_t *bd = cfg->context; + + // check if erase is valid + LFS_ASSERT(block < cfg->block_count); + + // block bad? + if (bd->cfg->erase_cycles) { + if (bd->wear[block] >= bd->cfg->erase_cycles) { + if (bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOERASE) { + LFS_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + } + } else { + // mark wear + bd->wear[block] += 1; + } + } + + // erase + int err = lfs_testbd_rawerase(cfg, block); + if (err) { + LFS_TRACE("lfs_testbd_erase -> %d", err); + return err; + } + + // 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_testbd_rawsync(cfg); + // simulate power loss + exit(33); + } + } + + LFS_TRACE("lfs_testbd_prog -> %d", 0); + return 0; +} + +int lfs_testbd_sync(const struct lfs_config *cfg) { + LFS_TRACE("lfs_testbd_sync(%p)", (void*)cfg); + int err = lfs_testbd_rawsync(cfg); + LFS_TRACE("lfs_testbd_sync -> %d", err); + return err; +} + + +/// simulated wear operations /// +lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, + lfs_block_t block) { + LFS_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_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]); + return bd->wear[block]; +} + +int lfs_testbd_setwear(const struct lfs_config *cfg, + lfs_block_t block, lfs_testbd_wear_t wear) { + LFS_TRACE("lfs_testbd_setwear(%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); + + bd->wear[block] = wear; + + LFS_TRACE("lfs_testbd_setwear -> %d", 0); + return 0; +} diff --git a/testbd/lfs_testbd.h b/testbd/lfs_testbd.h new file mode 100644 index 0000000..9235457 --- /dev/null +++ b/testbd/lfs_testbd.h @@ -0,0 +1,130 @@ +/* + * Testing block device, wraps filebd and rambd while providing a bunch + * of hooks for testing littlefs in various conditions. + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_TESTBD_H +#define LFS_TESTBD_H + +#include "lfs.h" +#include "lfs_util.h" +#include "rambd/lfs_rambd.h" +#include "filebd/lfs_filebd.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Mode determining how "bad blocks" behave during testing. This +// simulates some real-world circumstances such as writes not +// going through (noprog), erases not sticking (noerase), and ECC +// failures (noread). +enum lfs_testbd_badblock_behavior { + LFS_TESTBD_BADBLOCK_NOPROG = 0, + LFS_TESTBD_BADBLOCK_NOERASE = 1, + LFS_TESTBD_BADBLOCK_NOREAD = 2, +}; + +// Type for measuring wear +typedef uint32_t lfs_testbd_wear_t; +typedef int32_t lfs_testbd_swear_t; + +// testbd config, this is required for testing +struct lfs_testbd_config { + // 8-bit erase value to use for simulating erases. -1 does not simulate + // erases, which can speed up testing by avoiding all the extra block-device + // operations to store the erase value. + 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. + uint32_t erase_cycles; + + // The mode determining how bad blocks fail + uint8_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; + + // Optional buffer for RAM block device. + void *buffer; + + // Optional buffer for wear + void *wear_buffer; +}; + +// testbd state +typedef struct lfs_testbd { + union { + struct { + lfs_filebd_t bd; + struct lfs_filebd_config cfg; + } file; + struct { + lfs_rambd_t bd; + struct lfs_rambd_config cfg; + } ram; + } u; + + bool persist; + uint32_t power_cycles; + lfs_testbd_wear_t *wear; + + const struct lfs_testbd_config *cfg; +} lfs_testbd_t; + + +/// Block device API /// + +// Create a test block device using the geometry in lfs_config +// +// Note that filebd is used if a path is provided, if path is NULL +// testbd will use rambd which can be much faster. +int lfs_testbd_create(const struct lfs_config *cfg, const char *path); +int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, + const struct lfs_testbd_config *bdcfg); + +// Clean up memory associated with block device +int lfs_testbd_destroy(const struct lfs_config *cfg); + +// Read a block +int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + +// Program a block +// +// The block must have previously been erased. +int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + +// Erase a block +// +// A block must be erased before being programmed. The +// state of an erased block is undefined. +int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block); + +// Sync the block device +int lfs_testbd_sync(const struct lfs_config *cfg); + + +/// Additional extended API for driving test features /// + +// Get simulated wear on a given block +lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, + lfs_block_t block); + +// Manually set simulated wear on a given block +int lfs_testbd_setwear(const struct lfs_config *cfg, + lfs_block_t block, lfs_testbd_wear_t wear); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/tests_/test_alloc.toml b/tests_/test_alloc.toml index ce80411..8bfb5ed 100644 --- a/tests_/test_alloc.toml +++ b/tests_/test_alloc.toml @@ -503,6 +503,8 @@ code = ''' lfs_file_write(&lfs, &file, buffer, size) => size; } lfs_file_close(&lfs, &file) => 0; + + lfs_unmount(&lfs) => 0; ''' [[case]] # outdated lookahead and split dir test diff --git a/tests_/test_attrs.toml b/tests_/test_attrs.toml index f125418..db8d0c7 100644 --- a/tests_/test_attrs.toml +++ b/tests_/test_attrs.toml @@ -85,8 +85,8 @@ code = ''' lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); lfs_file_close(&lfs, &file); lfs_unmount(&lfs) => 0; - lfs_mount(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; memset(buffer, 0, sizeof(buffer)); lfs_setattr(&lfs, "/", 'A', "aaaa", 4) => 0; lfs_setattr(&lfs, "/", 'B', "bbbbbb", 6) => 0; @@ -162,7 +162,6 @@ code = ''' lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); lfs_file_close(&lfs, &file); lfs_unmount(&lfs) => 0; - lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; memset(buffer, 0, sizeof(buffer)); diff --git a/tests_/test_badblocks.toml b/tests_/test_badblocks.toml index 7599647..7969d43 100644 --- a/tests_/test_badblocks.toml +++ b/tests_/test_badblocks.toml @@ -1,50 +1,12 @@ -# special bad-block block device hook for simulating blocks -# that are no longer writable -code = ''' -lfs_block_t *bbbd_badblocks; -size_t bbbd_badblocks_count; - -int bbbd_read(const struct lfs_config *c, lfs_block_t block, - lfs_off_t off, void *buffer, lfs_size_t size) { - for (size_t i = 0; i < bbbd_badblocks_count; i++) { - if (bbbd_badblocks[i] == block) { - return LFS_ERR_CORRUPT; - } - } - - return lfs_testbd_read(c, block, off, buffer, size); -} - -int bbbd_prog(const struct lfs_config *c, lfs_block_t block, - lfs_off_t off, const void *buffer, lfs_size_t size) { - for (size_t i = 0; i < bbbd_badblocks_count; i++) { - if (bbbd_badblocks[i] == block) { - return LFS_ERR_CORRUPT; - } - } - - return lfs_testbd_prog(c, block, off, buffer, size); -} - -int bbbd_erase(const struct lfs_config *c, lfs_block_t block) { - for (size_t i = 0; i < bbbd_badblocks_count; i++) { - if (bbbd_badblocks[i] == block) { - return LFS_ERR_CORRUPT; - } - } - - return lfs_testbd_erase(c, block); -} -''' - [[case]] # single bad blocks -define.LFS_BD_PROG = 'bbbd_prog' +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOPROG' define.NAMEMULT = 64 define.FILEMULT = 1 code = ''' for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { - bbbd_badblocks = &(lfs_block_t){badblock}; - bbbd_badblocks_count = 1; + lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; + lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; lfs_format(&lfs, &cfg) => 0; @@ -103,13 +65,14 @@ code = ''' ''' [[case]] # single persistent blocks (can't erase) -define.LFS_BD_ERASE = 'bbbd_erase' +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOERASE' define.NAMEMULT = 64 define.FILEMULT = 1 code = ''' for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { - bbbd_badblocks = &(lfs_block_t){badblock}; - bbbd_badblocks_count = 1; + lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; + lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; lfs_format(&lfs, &cfg) => 0; @@ -168,13 +131,14 @@ code = ''' ''' [[case]] # single unreadable blocks (can't read) -define.LFS_BD_READ = 'bbbd_read' +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOREAD' define.NAMEMULT = 64 define.FILEMULT = 1 code = ''' - for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { - bbbd_badblocks = &(lfs_block_t){badblock}; - bbbd_badblocks_count = 1; + for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT/2; badblock++) { + lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; + lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; lfs_format(&lfs, &cfg) => 0; @@ -233,17 +197,17 @@ code = ''' ''' [[case]] # region corruption (causes cascading failures) -define.LFS_BD_PROG = '"BADTYPE == 0 ? bbbd_prog : lfs_testbd_prog "' -define.LFS_BD_ERASE = '"BADTYPE == 1 ? bbbd_erase : lfs_testbd_erase"' -define.LFS_BD_READ = '"BADTYPE == 2 ? bbbd_read : lfs_testbd_read "' -define.BADTYPE = 'range(3)' +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_NOPROG', + 'LFS_TESTBD_BADBLOCK_NOERASE', + 'LFS_TESTBD_BADBLOCK_NOREAD', +] define.NAMEMULT = 64 define.FILEMULT = 1 code = ''' - bbbd_badblocks_count = LFS_BLOCK_COUNT/2; - bbbd_badblocks = malloc(bbbd_badblocks_count*sizeof(lfs_block_t)); - for (size_t i = 0; i < bbbd_badblocks_count; i++) { - bbbd_badblocks[i] = i+2; + for (lfs_block_t i = 0; i < (LFS_BLOCK_COUNT-2)/2; i++) { + lfs_testbd_setwear(&cfg, i+2, 0xffffffff) => 0; } lfs_format(&lfs, &cfg) => 0; @@ -299,22 +263,20 @@ code = ''' lfs_file_close(&lfs, &file) => 0; } lfs_unmount(&lfs) => 0; - - free(bbbd_badblocks); ''' [[case]] # alternating corruption (causes cascading failures) -define.LFS_BD_PROG = '"BADTYPE == 0 ? bbbd_prog : lfs_testbd_prog "' -define.LFS_BD_ERASE = '"BADTYPE == 1 ? bbbd_erase : lfs_testbd_erase"' -define.LFS_BD_READ = '"BADTYPE == 2 ? bbbd_read : lfs_testbd_read "' -define.BADTYPE = 'range(3)' +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_NOPROG', + 'LFS_TESTBD_BADBLOCK_NOERASE', + 'LFS_TESTBD_BADBLOCK_NOREAD', +] define.NAMEMULT = 64 define.FILEMULT = 1 code = ''' - bbbd_badblocks_count = LFS_BLOCK_COUNT/2; - bbbd_badblocks = malloc(bbbd_badblocks_count*sizeof(lfs_block_t)); - for (size_t i = 0; i < bbbd_badblocks_count; i++) { - bbbd_badblocks[i] = (2*i) + 2; + for (lfs_block_t i = 0; i < (LFS_BLOCK_COUNT-2)/2; i++) { + lfs_testbd_setwear(&cfg, (2*i) + 2, 0xffffffff) => 0; } lfs_format(&lfs, &cfg) => 0; @@ -370,6 +332,20 @@ code = ''' lfs_file_close(&lfs, &file) => 0; } lfs_unmount(&lfs) => 0; - - free(bbbd_badblocks); +''' + +# other corner cases +[[case]] # bad superblocks (corrupt 1 or 0) +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_NOPROG', + 'LFS_TESTBD_BADBLOCK_NOERASE', + 'LFS_TESTBD_BADBLOCK_NOREAD', +] +code = ''' + lfs_testbd_setwear(&cfg, 0, 0xffffffff) => 0; + lfs_testbd_setwear(&cfg, 1, 0xffffffff) => 0; + + lfs_format(&lfs, &cfg) => LFS_ERR_NOSPC; + lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; ''' diff --git a/tests_/test_exhaustion.toml b/tests_/test_exhaustion.toml new file mode 100644 index 0000000..8dd3d15 --- /dev/null +++ b/tests_/test_exhaustion.toml @@ -0,0 +1,341 @@ +[[case]] # test running a filesystem to exhaustion +define.LFS_ERASE_CYCLES = 10 +define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster +define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_NOPROG', + 'LFS_TESTBD_BADBLOCK_NOERASE', + 'LFS_TESTBD_BADBLOCK_NOREAD', +] +define.FILES = 10 +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "roadrunner") => 0; + lfs_unmount(&lfs) => 0; + + uint32_t cycle = 0; + while (true) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + LFS_WARN("completed %d cycles", cycle); +''' + +[[case]] # test running a filesystem to exhaustion + # which also requires expanding superblocks +define.LFS_ERASE_CYCLES = 10 +define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster +define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_NOPROG', + 'LFS_TESTBD_BADBLOCK_NOERASE', + 'LFS_TESTBD_BADBLOCK_NOREAD', +] +define.FILES = 10 +code = ''' + lfs_format(&lfs, &cfg) => 0; + + uint32_t cycle = 0; + while (true) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + LFS_WARN("completed %d cycles", cycle); +''' + +# These are a sort of high-level litmus test for wear-leveling. One definition +# of wear-leveling is that increasing a block device's space translates directly +# into increasing the block devices lifetime. This is something we can actually +# check for. + +[[case]] # wear-level test running a filesystem to exhaustion +define.LFS_ERASE_CYCLES = 10 +define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster +define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_NOPROG', + 'LFS_TESTBD_BADBLOCK_NOERASE', + 'LFS_TESTBD_BADBLOCK_NOREAD', +] +define.FILES = 10 +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "roadrunner") => 0; + lfs_unmount(&lfs) => 0; + + uint32_t run_cycles[2]; + const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT}; + + for (int run = 0; run < 2; run++) { + for (lfs_block_t b = 0; b < LFS_BLOCK_COUNT; b++) { + lfs_testbd_setwear(&cfg, b, + (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0; + } + + uint32_t cycle = 0; + while (true) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + run_cycles[run] = cycle; + LFS_WARN("completed %d blocks %d cycles", + run_block_count[run], run_cycles[run]); + } + + // check we increased the lifetime by 2x with ~5% error + LFS_ASSERT(run_cycles[1] > 2*run_cycles[0]-run_cycles[0]/20); +''' + +[[case]] # wear-level test + expanding superblock +define.LFS_ERASE_CYCLES = 10 +define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster +define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_NOPROG', + 'LFS_TESTBD_BADBLOCK_NOERASE', + 'LFS_TESTBD_BADBLOCK_NOREAD', +] +define.FILES = 10 +code = ''' + lfs_format(&lfs, &cfg) => 0; + + uint32_t run_cycles[2]; + const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT}; + + for (int run = 0; run < 2; run++) { + for (lfs_block_t b = 0; b < LFS_BLOCK_COUNT; b++) { + lfs_testbd_setwear(&cfg, b, + (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0; + } + + uint32_t cycle = 0; + while (true) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + run_cycles[run] = cycle; + LFS_WARN("completed %d blocks %d cycles", + run_block_count[run], run_cycles[run]); + } + + // check we increased the lifetime by 2x with ~5% error + LFS_ASSERT(run_cycles[1] > 2*run_cycles[0]-run_cycles[0]/20); +''' diff --git a/tests_/test_format.toml b/tests_/test_format.toml index 702c039..7932d54 100644 --- a/tests_/test_format.toml +++ b/tests_/test_format.toml @@ -26,8 +26,6 @@ code = ''' lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; ''' -# TODO invalid superblock? (corrupt 1, 0) - [[case]] # expanding superblock define.BLOCK_CYCLES = [32, 33, 1] define.N = [10, 100, 1000] diff --git a/tests_/test_relocations.toml b/tests_/test_relocations.toml new file mode 100644 index 0000000..1fa73e5 --- /dev/null +++ b/tests_/test_relocations.toml @@ -0,0 +1,143 @@ +# specific corner cases worth explicitly testing for + +[[case]] # dangling split dir test +define.ITERATIONS = 20 +define.COUNT = 10 +code = ''' + lfs_format(&lfs, &cfg) => 0; + // fill up filesystem so only ~16 blocks are left + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; + memset(buffer, 0, 512); + while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { + lfs_file_write(&lfs, &file, buffer, 512) => 512; + } + lfs_file_close(&lfs, &file) => 0; + // make a child dir to use in bounded space + lfs_mkdir(&lfs, "child") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int j = 0; j < ITERATIONS; j++) { + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_close(&lfs, &file) => 0; + } + + lfs_dir_open(&lfs, &dir, "child") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + if (j == ITERATIONS-1) { + break; + } + + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_remove(&lfs, path) => 0; + } + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "child") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_remove(&lfs, path) => 0; + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # outdated head test +define.ITERATIONS = 20 +define.COUNT = 10 +code = ''' + lfs_format(&lfs, &cfg) => 0; + // fill up filesystem so only ~16 blocks are left + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; + memset(buffer, 0, 512); + while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { + lfs_file_write(&lfs, &file, buffer, 512) => 512; + } + lfs_file_close(&lfs, &file) => 0; + // make a child dir to use in bounded space + lfs_mkdir(&lfs, "child") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int j = 0; j < ITERATIONS; j++) { + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_close(&lfs, &file) => 0; + } + + lfs_dir_open(&lfs, &dir, "child") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + info.size => 0; + + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hi", 2) => 2; + lfs_file_close(&lfs, &file) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + + lfs_dir_rewind(&lfs, &dir) => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + info.size => 2; + + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hi", 2) => 2; + lfs_file_close(&lfs, &file) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + + lfs_dir_rewind(&lfs, &dir) => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + info.size => 2; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_remove(&lfs, path) => 0; + } + } + lfs_unmount(&lfs) => 0; +'''