Ver Fonte

Migrated the first of the tests with internal knowledge

Both test_move and test_orphan needed internal knowledge which comes
with the addition of the "in" attribute. This was in the plan for the
test-revamp from the beginning as it really opens up the ability to
write more unit-style-tests using internal knowledge of how littlefs
works. More unit-style-tests should help _fix_ bugs by limiting the
scope of the test and where the bug could be hiding.

The "in" attribute effectively runs tests _inside_ the .c file
specified, giving the test access to all static members without
needed to change their visibility.
Christopher Haster há 6 anos atrás
pai
commit
5181ce66cd

+ 129 - 105
scripts/test_.py

@@ -5,18 +5,20 @@
 #
 
 # TODO
-# - nargs > 1?
+# x nargs > 1?
 # x show perm config on failure
 # x filtering
 # n show perm config on verbose?
-# - better lineno tracking for cases?
+# x better lineno tracking for cases?
 # n non-int perms?
-# - different path format?
+# x different path format?
 # - suite.prologue, suite.epilogue
-# - in
+# x in
 # x change BLOCK_CYCLES to -1 by default
 # x change persist behaviour
 # x config chaining correct
+# - why can't gdb see my defines?
+# - say no to internal?
 
 import toml
 import glob
@@ -34,17 +36,14 @@ import shlex
 TESTDIR = 'tests_'
 RULES = """
 define FLATTEN
-%$(subst /,.,$(target:.c=.tc)): $(target)
-    cat <(echo '#line 1 "$$<"') $$< > $$@
+tests_/%$(subst /,.,$(target)): $(target)
+    ./scripts/explode_asserts.py $$< -o $$@
 endef
 $(foreach target,$(SRC),$(eval $(FLATTEN)))
 
 -include tests_/*.d
 
 .SECONDARY:
-%.c: %.tc
-    ./scripts/explode_asserts.py $< -o $@
-
 %.test: override CFLAGS += -fdiagnostics-color=always
 %.test: override CFLAGS += -ggdb
 %.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f)
@@ -56,7 +55,6 @@ GLOBALS = """
 #include "filebd/lfs_filebd.h"
 #include "rambd/lfs_rambd.h"
 #include <stdio.h>
-const char *LFS_DISK = NULL;
 """
 DEFINES = {
     "LFS_READ_SIZE": 16,
@@ -70,6 +68,8 @@ DEFINES = {
 }
 PROLOGUE = """
     // prologue
+    extern const char *LFS_DISK;
+
     __attribute__((unused)) lfs_t lfs;
     __attribute__((unused)) lfs_filebd_t filebd;
     __attribute__((unused)) lfs_rambd_t rambd;
@@ -129,28 +129,31 @@ class TestFailure(Exception):
         self.assert_ = assert_
 
 class TestCase:
-    def __init__(self, config, suite=None, caseno=None, lineno=None, **_):
+    def __init__(self, config, filter=filter,
+            suite=None, caseno=None, lineno=None, **_):
+        self.filter = filter
         self.suite = suite
         self.caseno = caseno
         self.lineno = lineno
 
         self.code = config['code']
+        self.code_lineno = config['code_lineno']
         self.defines = config.get('define', {})
         self.if_ = config.get('if', None)
-        self.leaky = config.get('leaky', False)
+        self.in_ = config.get('in', None)
 
     def __str__(self):
         if hasattr(self, 'permno'):
             if any(k not in self.case.defines for k in self.defines):
-                return '%s[%d,%d] (%s)' % (
+                return '%s#%d#%d (%s)' % (
                     self.suite.name, self.caseno, self.permno, ', '.join(
                         '%s=%s' % (k, v) for k, v in self.defines.items()
                         if k not in self.case.defines))
             else:
-                return '%s[%d,%d]' % (
+                return '%s#%d#%d' % (
                     self.suite.name, self.caseno, self.permno)
         else:
-            return '%s[%d]' % (
+            return '%s#%d' % (
                 self.suite.name, self.caseno)
 
     def permute(self, defines, permno=None, **_):
@@ -163,17 +166,10 @@ class TestCase:
 
     def build(self, f, **_):
         # prologue
-        f.write('void test_case%d(' % self.caseno)
-        first = True
-        for k, v in sorted(self.perms[0].defines.items()):
-            if k not in self.defines:
-                if not first:
-                    f.write(',')
-                else:
-                    first = False
-                f.write('\n')
-                f.write(8*' '+'__attribute__((unused)) intmax_t %s' % k)
-        f.write(') {\n')
+        f.write('void test_case%d(%s) {\n' % (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:
@@ -182,7 +178,7 @@ class TestCase:
         f.write(PROLOGUE)
         f.write('\n')
         f.write(4*' '+'// test case %d\n' % self.caseno)
-        f.write(4*' '+'#line %d "%s"\n' % (self.lineno, self.suite.path))
+        f.write(4*' '+'#line %d "%s"\n' % (self.code_lineno, self.suite.path))
 
         # test case goes here
         f.write(self.code)
@@ -198,7 +194,15 @@ class TestCase:
         f.write('}\n')
 
     def shouldtest(self, **args):
-        if self.if_ is not None:
+        if (self.filter is not None and
+                len(self.filter) >= 1 and
+                self.filter[0] != self.caseno):
+            return False
+        elif (self.filter is not None and
+                len(self.filter) >= 2 and
+                self.filter[1] != self.permno):
+            return False
+        elif self.if_ is not None:
             return eval(self.if_, None, self.defines.copy())
         else:
             return True
@@ -227,7 +231,7 @@ class TestCase:
                     ncmd.extend(['-ex', 'up'])
             elif gdb == 'start':
                 ncmd.extend([
-                    '-ex', 'b %s:%d' % (self.suite.path, self.lineno),
+                    '-ex', 'b %s:%d' % (self.suite.path, self.code_lineno),
                     '-ex', 'r'])
             ncmd.extend(['--args'] + cmd)
 
@@ -309,7 +313,7 @@ class ReentrantTestCase(TestCase):
         for cycles in it.count(1):
             # exact cycle we should drop into debugger?
             if gdb and failure and failure.cycleno == cycles:
-                return super().test(exec=exec, persist=True,
+                return super().test(exec=exec, persist='noerase',
                     gdb=gdb, failure=failure, **args)
 
             # run tests, but kill the program after prog/erase has
@@ -337,11 +341,12 @@ class ReentrantTestCase(TestCase):
                     raise
 
 class TestSuite:
-    def __init__(self, path, TestCase=TestCase, **args):
+    def __init__(self, path, filter=None, TestCase=TestCase, **args):
         self.name = os.path.basename(path)
         if self.name.endswith('.toml'):
             self.name = self.name[:-len('.toml')]
         self.path = path
+        self.filter = filter
         self.TestCase = TestCase
 
         with open(path) as f:
@@ -351,9 +356,12 @@ class TestSuite:
             # find line numbers
             f.seek(0)
             linenos = []
+            code_linenos = []
             for i, line in enumerate(f):
-                if re.match(r'^\s*code\s*=\s*(\'\'\'|""")', line):
-                    linenos.append(i + 2)
+                if re.match(r'\[\[\s*case\s*\]\]', line):
+                    linenos.append(i+1)
+                if re.match(r'code\s*=\s*(\'\'\'|""")', line):
+                    code_linenos.append(i+2)
 
         # grab global config
         self.defines = config.get('define', {})
@@ -361,12 +369,15 @@ class TestSuite:
         # create initial test cases
         self.cases = []
         for i, (case, lineno) in enumerate(zip(config['case'], linenos)):
+            # code lineno?
+            if 'code' in case:
+                case['code_lineno'] = code_linenos.pop(0)
             # give our case's config a copy of our "global" config
             for k, v in config.items():
                 if k not in case:
                     case[k] = v
             # initialize test case
-            self.cases.append(self.TestCase(case,
+            self.cases.append(self.TestCase(case, filter=filter,
                 suite=self, caseno=i, lineno=lineno, **args))
 
     def __str__(self):
@@ -438,40 +449,52 @@ class TestSuite:
         return self.perms
 
     def build(self, **args):
-        # build test.c
-        f = io.StringIO()
-        f.write(GLOBALS)
+        # build test files
+        tf = open(self.path + '.test.c.t', 'w')
+        tf.write(GLOBALS)
+        tfs = {None: tf}
 
         for case in self.cases:
-            f.write('\n')
-            case.build(f, **args)
-
-        f.write('\n')
-        f.write('int main(int argc, char **argv) {\n')
-        f.write(4*' '+'int case_ = (argc >= 2) ? atoi(argv[1]) : 0;\n')
-        f.write(4*' '+'int perm = (argc >= 3) ? atoi(argv[2]) : 0;\n')
-        f.write(4*' '+'LFS_DISK = (argc >= 4) ? argv[3] : NULL;\n')
+            if case.in_ not in tfs:
+                tfs[case.in_] = open(self.path+'.'+
+                    case.in_.replace('/', '.')+'.t', 'w')
+                tfs[case.in_].write('#line 1 "%s"\n' % case.in_)
+                with open(case.in_) as f:
+                    for line in f:
+                        tfs[case.in_].write(line)
+                tfs[case.in_].write('\n')
+                tfs[case.in_].write(GLOBALS)
+
+            tfs[case.in_].write('\n')
+            case.build(tfs[case.in_], **args)
+
+        tf.write('\n')
+        tf.write('const char *LFS_DISK = NULL;\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_DISK = (argc >= 4) ? argv[3] : NULL;\n')
         for perm in self.perms:
-            f.write(4*' '+'if (argc < 3 || '
-                '(case_ == %d && perm == %d)) { ' % (
-                    perm.caseno, perm.permno))
-            f.write('test_case%d(' % perm.caseno)
-            first = True
-            for k, v in sorted(perm.defines.items()):
-                if k not in perm.case.defines:
-                    if not first:
-                        f.write(', ')
-                    else:
-                        first = False
-                    f.write(str(v))
-            f.write('); }\n')
-        f.write('}\n')
-
-        # add test-related rules
-        rules = RULES.replace(4*' ', '\t')
-
+            # test declaration
+            tf.write(4*' '+'extern void test_case%d(%s);\n' % (
+                perm.caseno, ', '.join(
+                    'intmax_t %s' % k for k in sorted(perm.defines)
+                    if k not in perm.case.defines)))
+            # test call
+            tf.write(4*' '+
+                'if (argc < 3 || (case_ == %d && perm == %d)) {'
+                ' test_case%d(%s); '
+                '}\n' % (perm.caseno, perm.permno, perm.caseno, ', '.join(
+                    str(v) for k, v in sorted(perm.defines.items())
+                    if k not in perm.case.defines)))
+        tf.write('}\n')
+
+        for tf in tfs.values():
+            tf.close()
+
+        # write makefiles
         with open(self.path + '.mk', 'w') as mk:
-            mk.write(rules)
+            mk.write(RULES.replace(4*' ', '\t'))
             mk.write('\n')
 
             # add truely global defines globally
@@ -479,12 +502,18 @@ class TestSuite:
                 mk.write('%s: override CFLAGS += -D%s=%r\n' % (
                     self.path+'.test', k, v))
 
-            # write test.c in base64 so make can decide when to rebuild
-            mk.write('%s: %s\n' % (self.path+'.test.tc', self.path))
-            mk.write('\t@base64 -d <<< ')
-            mk.write(base64.b64encode(
-                f.getvalue().encode('utf8')).decode('utf8'))
-            mk.write(' > $@\n')
+            for path in tfs:
+                if path is None:
+                    mk.write('%s: %s | %s\n' % (
+                        self.path+'.test.c',
+                        self.path,
+                        self.path+'.test.c.t'))
+                else:
+                    mk.write('%s: %s %s | %s\n' % (
+                        self.path+'.'+path.replace('/', '.'),
+                        self.path, path,
+                        self.path+'.'+path.replace('/', '.')+'.t'))
+                mk.write('\t./scripts/explode_asserts.py $| -o $@\n')
 
         self.makefile = self.path + '.mk'
         self.target = self.path + '.test'
@@ -524,37 +553,33 @@ class TestSuite:
             sys.stdout.write('\n')
 
 def main(**args):
-    testpath = args['testpath']
-
-    # optional brackets for specific test
-    m = re.search(r'\[(\d+)(?:,(\d+))?\]$', testpath)
-    if m:
-        caseno = int(m.group(1))
-        permno = int(m.group(2)) if m.group(2) is not None else None
-        testpath = testpath[:m.start()]
-    else:
-        caseno = None
-        permno = None
-
-    # figure out the suite's toml file
-    if os.path.isdir(testpath):
-        testpath = testpath + '/test_*.toml'
-    elif os.path.isfile(testpath):
-        testpath = testpath
-    elif testpath.endswith('.toml'):
-        testpath = TESTDIR + '/' + testpath
-    else:
-        testpath = TESTDIR + '/' + testpath + '.toml'
-
-    # find tests
     suites = []
-    for path in glob.glob(testpath):
-        if args.get('valgrind', False):
-            suites.append(TestSuite(path, TestCase=ValgrindTestCase, **args))
-        elif args.get('reentrant', False):
-            suites.append(TestSuite(path, TestCase=ReentrantTestCase, **args))
+    for testpath in args['testpaths']:
+        # optionally specified test case/perm
+        testpath, *filter = testpath.split('#')
+        filter = [int(f) for f in filter]
+
+        # figure out the suite's toml file
+        if os.path.isdir(testpath):
+            testpath = testpath + '/test_*.toml'
+        elif os.path.isfile(testpath):
+            testpath = testpath
+        elif testpath.endswith('.toml'):
+            testpath = TESTDIR + '/' + testpath
         else:
-            suites.append(TestSuite(path, **args))
+            testpath = TESTDIR + '/' + testpath + '.toml'
+
+        # find tests
+        for path in glob.glob(testpath):
+            if args.get('valgrind', False):
+                TestCase_ = ValgrindTestCase
+            elif args.get('reentrant', False):
+                TestCase_ = ReentrantTestCase
+            else:
+                TestCase_ = TestCase
+
+            suites.append(TestSuite(path,
+                filter=filter, TestCase=TestCase_, **args))
 
     # sort for reproducability
     suites = sorted(suites)
@@ -632,7 +657,7 @@ def main(**args):
     print('====== testing ======')
     try:
         for suite in suites:
-            suite.test(caseno, permno, **args)
+            suite.test(**args)
     except TestFailure:
         pass
 
@@ -647,11 +672,10 @@ def main(**args):
             if perm.result == PASS:
                 passed += 1
             else:
-                #sys.stdout.write("--- %s ---\n" % perm)
                 sys.stdout.write(
                     "\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m "
                     "{perm} failed with {returncode}\n".format(
-                        perm=perm, path=perm.suite.path, lineno=perm.lineno-2,
+                        perm=perm, path=perm.suite.path, lineno=perm.lineno,
                         returncode=perm.result.returncode or 0))
                 if perm.result.stdout:
                     for line in (perm.result.stdout
@@ -681,15 +705,15 @@ def main(**args):
             failure.case.test(failure=failure, **args)
             sys.exit(0)
 
-
     print('tests passed: %d' % passed)
     print('tests failed: %d' % failed)
+    return 1 if failed > 0 else 0
 
 if __name__ == "__main__":
     import argparse
     parser = argparse.ArgumentParser(
         description="Run parameterized tests in various configurations.")
-    parser.add_argument('testpath', nargs='?', default=TESTDIR,
+    parser.add_argument('testpaths', nargs='*', default=[TESTDIR],
         help="Description of test(s) to run. By default, this is all tests \
             found in the \"{0}\" directory. Here, you can specify a different \
             directory of tests, a specific file, a suite by name, and even a \
@@ -713,4 +737,4 @@ if __name__ == "__main__":
         help="Run reentrant tests with simulated power-loss.")
     parser.add_argument('-e', '--exec', default=[], type=lambda e: e.split(' '),
         help="Run tests with another executable prefixed on the command line.")
-    main(**vars(parser.parse_args()))
+    sys.exit(main(**vars(parser.parse_args())))

+ 19 - 19
tests_/test_alloc.toml

@@ -2,6 +2,8 @@
 # note for these to work there are many constraints on the device geometry
 
 [[case]] # parallel allocation test
+define.FILES = 3
+define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / FILES)'
 code = '''
     const char *names[FILES] = {"bacon", "eggs", "pancakes"};
     lfs_file_t files[FILES];
@@ -41,10 +43,10 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 '''
-define.FILES = 3
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / FILES)'
 
 [[case]] # serial allocation test
+define.FILES = 3
+define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / FILES)'
 code = '''
     const char *names[FILES] = {"bacon", "eggs", "pancakes"};
 
@@ -80,10 +82,11 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 '''
-define.FILES = 3
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / FILES)'
 
 [[case]] # parallel allocation reuse test
+define.FILES = 3
+define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / FILES)'
+define.CYCLES = [1, 10]
 code = '''
     const char *names[FILES] = {"bacon", "eggs", "pancakes"};
     lfs_file_t files[FILES];
@@ -134,11 +137,11 @@ code = '''
         lfs_unmount(&lfs) => 0;
     }
 '''
+
+[[case]] # serial allocation reuse test
 define.FILES = 3
 define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / FILES)'
 define.CYCLES = [1, 10]
-
-[[case]] # serial allocation reuse test
 code = '''
     const char *names[FILES] = {"bacon", "eggs", "pancakes"};
 
@@ -185,9 +188,6 @@ code = '''
         lfs_unmount(&lfs) => 0;
     }
 '''
-define.FILES = 3
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / FILES)'
-define.CYCLES = [1, 10]
 
 [[case]] # exhaustion test
 code = '''
@@ -226,6 +226,7 @@ code = '''
 '''
 
 [[case]] # exhaustion wraparound test
+define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / 3)'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -271,7 +272,6 @@ code = '''
     lfs_remove(&lfs, "exhaustion") => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / 3)'
 
 [[case]] # dir exhaustion test
 code = '''
@@ -327,6 +327,9 @@ code = '''
 # should be removed and replaced with generalized tests.
 
 [[case]] # chained dir exhaustion test
+define.LFS_BLOCK_SIZE = 512
+define.LFS_BLOCK_COUNT = 1024
+if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -393,11 +396,11 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
+
+[[case]] # split dir test
 define.LFS_BLOCK_SIZE = 512
 define.LFS_BLOCK_COUNT = 1024
 if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'
-
-[[case]] # split dir test
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -438,11 +441,11 @@ code = '''
 
     lfs_unmount(&lfs) => 0;
 '''
+
+[[case]] # outdated lookahead test
 define.LFS_BLOCK_SIZE = 512
 define.LFS_BLOCK_COUNT = 1024
 if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'
-
-[[case]] # outdated lookahead test
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -501,11 +504,11 @@ code = '''
     }
     lfs_file_close(&lfs, &file) => 0;
 '''
+
+[[case]] # outdated lookahead and split dir test
 define.LFS_BLOCK_SIZE = 512
 define.LFS_BLOCK_COUNT = 1024
 if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'
-
-[[case]] # outdated lookahead and split dir test
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -561,6 +564,3 @@ code = '''
 
     lfs_unmount(&lfs) => 0;
 '''
-define.LFS_BLOCK_SIZE = 512
-define.LFS_BLOCK_COUNT = 1024
-if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'

+ 13 - 13
tests_/test_dirs.toml

@@ -15,6 +15,7 @@ code = '''
 '''
 
 [[case]] # many directory creation
+define.N = 'range(0, 100, 3)'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -43,9 +44,9 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.N = 'range(0, 100, 3)'
 
 [[case]] # many directory removal
+define.N = 'range(3, 100, 11)'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -93,9 +94,9 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.N = 'range(3, 100, 11)'
 
 [[case]] # many directory rename
+define.N = 'range(3, 100, 11)'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -152,9 +153,10 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
 '''
-define.N = 'range(3, 100, 11)'
 
 [[case]] # reentrant many directory creation/rename/removal
+define.N = [5, 25]
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -231,10 +233,9 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.N = [5, 25]
-reentrant = true
 
 [[case]] # file creation
+define.N = 'range(3, 100, 11)'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -265,9 +266,9 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
 '''
-define.N = 'range(3, 100, 11)'
 
 [[case]] # file removal
+define.N = 'range(0, 100, 3)'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -317,9 +318,9 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.N = 'range(0, 100, 3)'
 
 [[case]] # file rename
+define.N = 'range(0, 100, 3)'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -378,9 +379,10 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
 '''
-define.N = 'range(0, 100, 3)'
 
 [[case]] # reentrant file creation/rename/removal
+define.N = [5, 25]
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -457,8 +459,6 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.N = [5, 25]
-reentrant = true
 
 [[case]] # nested directories
 code = '''
@@ -585,6 +585,7 @@ code = '''
 '''
 
 [[case]] # recursive remove
+define.N = [10, 100]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -639,7 +640,6 @@ code = '''
     lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
     lfs_unmount(&lfs) => 0;
 '''
-define.N = [10, 100]
 
 [[case]] # other error cases
 code = '''
@@ -716,6 +716,7 @@ code = '''
 '''
 
 [[case]] # directory seek
+define.COUNT = [4, 128, 132]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -774,9 +775,9 @@ code = '''
         lfs_unmount(&lfs) => 0;
     }
 '''
-define.COUNT = [4, 128, 132]
 
 [[case]] # root seek
+define.COUNT = [4, 128, 132]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -834,5 +835,4 @@ code = '''
         lfs_unmount(&lfs) => 0;
     }
 '''
-define.COUNT = [4, 128, 132]
 

+ 25 - 25
tests_/test_files.toml

@@ -20,6 +20,8 @@ code = '''
 '''
 
 [[case]] # larger files
+define.SIZE = [32, 8192, 262144, 0, 7, 8193]
+define.CHUNKSIZE = [31, 16, 33, 1, 1023]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -54,10 +56,11 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.SIZE = [32, 8192, 262144, 0, 7, 8193]
-define.CHUNKSIZE = [31, 16, 33, 1, 1023]
 
 [[case]] # rewriting files
+define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
+define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
+define.CHUNKSIZE = [31, 16, 1, 1025]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -135,11 +138,11 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
+
+[[case]] # appending files
 define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
 define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
 define.CHUNKSIZE = [31, 16, 1, 1025]
-
-[[case]] # appending files
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -212,11 +215,11 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
+
+[[case]] # truncating files
 define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
 define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
 define.CHUNKSIZE = [31, 16, 1, 1025]
-
-[[case]] # truncating files
 code = '''
     lfs_format(&lfs, &cfg) => 0;
 
@@ -281,11 +284,11 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
-define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
-define.CHUNKSIZE = [31, 16, 1, 1025]
 
 [[case]] # reentrant file writing
+define.SIZE = [32, 0, 7, 2049]
+define.CHUNKSIZE = [31, 16, 65]
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -329,11 +332,17 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.SIZE = [32, 0, 7, 2049]
-define.CHUNKSIZE = [31, 16, 65]
-reentrant = true
 
 [[case]] # reentrant file writing with syncs
+define = [
+    # append (O(n))
+    {MODE='LFS_O_APPEND',   SIZE=[32, 0, 7, 2049],  CHUNKSIZE=[31, 16, 65]},
+    # truncate (O(n^2))
+    {MODE='LFS_O_TRUNC',    SIZE=[32, 0, 7, 200],   CHUNKSIZE=[31, 16, 65]},
+    # rewrite (O(n^2))
+    {MODE=0,                SIZE=[32, 0, 7, 200],   CHUNKSIZE=[31, 16, 65]},
+]
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -393,17 +402,9 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define = [
-    # append (O(n))
-    {MODE='LFS_O_APPEND',   SIZE=[32, 0, 7, 2049],  CHUNKSIZE=[31, 16, 65]},
-    # truncate (O(n^2))
-    {MODE='LFS_O_TRUNC',    SIZE=[32, 0, 7, 200],   CHUNKSIZE=[31, 16, 65]},
-    # rewrite (O(n^2))
-    {MODE=0,                SIZE=[32, 0, 7, 200],   CHUNKSIZE=[31, 16, 65]},
-]
-reentrant = true
 
 [[case]] # many files
+define.N = 300
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     // create N files of 7 bytes
@@ -426,9 +427,9 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 '''
-define.N = 300
 
 [[case]] # many files with power cycle
+define.N = 300
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     // create N files of 7 bytes
@@ -453,9 +454,10 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 '''
-define.N = 300
 
 [[case]] # many files with power loss
+define.N = 300
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -482,5 +484,3 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 '''
-define.N = 300
-reentrant = true

+ 8 - 8
tests_/test_format.toml

@@ -11,6 +11,7 @@ code = '''
 '''
 
 [[case]] # reentrant format
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -19,7 +20,6 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 '''
-reentrant = true
 
 [[case]] # invalid mount
 code = '''
@@ -29,6 +29,8 @@ code = '''
 # TODO invalid superblock? (corrupt 1, 0)
 
 [[case]] # expanding superblock
+define.BLOCK_CYCLES = [32, 33, 1]
+define.N = [10, 100, 1000]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -47,10 +49,10 @@ code = '''
     assert(strcmp(info.name, "dummy") == 0);
     lfs_unmount(&lfs) => 0;
 '''
-define.BLOCK_CYCLES = [32, 33, 1]
-define.N = [10, 100, 1000]
 
 [[case]] # expanding superblock with power cycle
+define.BLOCK_CYCLES = [32, 33, 1]
+define.N = [10, 100, 1000]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     for (int i = 0; i < N; i++) {
@@ -71,10 +73,11 @@ code = '''
     assert(strcmp(info.name, "dummy") == 0);
     lfs_unmount(&lfs) => 0;
 '''
-define.BLOCK_CYCLES = [32, 33, 1]
-define.N = [10, 100, 1000]
 
 [[case]] # reentrant expanding superblock
+define.BLOCK_CYCLES = [2, 1]
+define.N = 24
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -100,6 +103,3 @@ code = '''
     assert(strcmp(info.name, "dummy") == 0);
     lfs_unmount(&lfs) => 0;
 '''
-define.BLOCK_CYCLES = [2, 1]
-define.N = 24
-reentrant = true

+ 26 - 26
tests_/test_interspersed.toml

@@ -1,5 +1,16 @@
 
 [[case]] # interspersed file test
+# TODO FILES=26 found bug
+#define.SIZE = [10, 100]
+#define.FILES = [4, 10, 26] 
+define = [
+    {SIZE=10, FILES=4},
+    {SIZE=10, FILES=10},
+    #{SIZE=10, FILES=26},
+    {SIZE=100, FILES=4},
+    {SIZE=100, FILES=10},
+    #{SIZE=100, FILES=26},
+]
 code = '''
     lfs_file_t files[FILES];
     const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
@@ -56,19 +67,10 @@ code = '''
     
     lfs_unmount(&lfs) => 0;
 '''
-# TODO FILES=26 found bug
-#define.SIZE = [10, 100]
-#define.FILES = [4, 10, 26] 
-define = [
-    {SIZE=10, FILES=4},
-    {SIZE=10, FILES=10},
-    #{SIZE=10, FILES=26},
-    {SIZE=100, FILES=4},
-    {SIZE=100, FILES=10},
-    #{SIZE=100, FILES=26},
-]
 
 [[case]] # interspersed remove file test
+define.SIZE = [10, 100]
+define.FILES = [4, 10, 26]
 code = '''
     const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
     lfs_format(&lfs, &cfg) => 0;
@@ -118,10 +120,9 @@ code = '''
     
     lfs_unmount(&lfs) => 0;
 '''
-define.SIZE = [10, 100]
-define.FILES = [4, 10, 26]
 
 [[case]] # remove inconveniently test
+define.SIZE = [10, 100]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -179,9 +180,20 @@ code = '''
     
     lfs_unmount(&lfs) => 0;
 '''
-define.SIZE = [10, 100]
 
 [[case]] # reentrant interspersed file test
+# TODO FILES=26 found bug
+#define.SIZE = [10, 100]
+#define.FILES = [4, 10, 26] 
+define = [
+    {SIZE=10, FILES=4},
+    {SIZE=10, FILES=10},
+    #{SIZE=10, FILES=26},
+    {SIZE=100, FILES=4},
+    #{SIZE=100, FILES=10},
+    #{SIZE=100, FILES=26},
+]
+reentrant = true
 code = '''
     lfs_file_t files[FILES];
     const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
@@ -248,15 +260,3 @@ code = '''
     
     lfs_unmount(&lfs) => 0;
 '''
-# TODO FILES=26 found bug
-#define.SIZE = [10, 100]
-#define.FILES = [4, 10, 26] 
-define = [
-    {SIZE=10, FILES=4},
-    {SIZE=10, FILES=10},
-    #{SIZE=10, FILES=26},
-    {SIZE=100, FILES=4},
-    #{SIZE=100, FILES=10},
-    #{SIZE=100, FILES=26},
-]
-reentrant = true

+ 957 - 0
tests_/test_move.toml

@@ -0,0 +1,957 @@
+[[case]] # move file
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hola\n", 5) => 5;
+    lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8;
+    lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hello", "c/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hello") == 0);
+    assert(info.type == LFS_TYPE_REG);
+    assert(info.size == 5+8+6);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file, buffer, 5) => 5;
+    memcmp(buffer, "hola\n", 5) => 0;
+    lfs_file_read(&lfs, &file, buffer, 8) => 8;
+    memcmp(buffer, "bonjour\n", 8) => 0;
+    lfs_file_read(&lfs, &file, buffer, 6) => 6;
+    memcmp(buffer, "ohayo\n", 6) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # move file corrupt source
+in = "lfs.c"
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hola\n", 5) => 5;
+    lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8;
+    lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hello", "c/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // corrupt the source
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_block_t block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    uint8_t bbuffer[LFS_BLOCK_SIZE];
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    int off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hello") == 0);
+    assert(info.type == LFS_TYPE_REG);
+    assert(info.size == 5+8+6);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file, buffer, 5) => 5;
+    memcmp(buffer, "hola\n", 5) => 0;
+    lfs_file_read(&lfs, &file, buffer, 8) => 8;
+    memcmp(buffer, "bonjour\n", 8) => 0;
+    lfs_file_read(&lfs, &file, buffer, 6) => 6;
+    memcmp(buffer, "ohayo\n", 6) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # move file corrupt source and dest
+in = "lfs.c"
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hola\n", 5) => 5;
+    lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8;
+    lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hello", "c/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // corrupt the source
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_block_t block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    uint8_t bbuffer[LFS_BLOCK_SIZE];
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    int off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    // corrupt the destination
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hello") == 0);
+    assert(info.type == LFS_TYPE_REG);
+    assert(info.size == 5+8+6);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file, buffer, 5) => 5;
+    memcmp(buffer, "hola\n", 5) => 0;
+    lfs_file_read(&lfs, &file, buffer, 8) => 8;
+    memcmp(buffer, "bonjour\n", 8) => 0;
+    lfs_file_read(&lfs, &file, buffer, 6) => 6;
+    memcmp(buffer, "ohayo\n", 6) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # move file after corrupt
+in = "lfs.c"
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hola\n", 5) => 5;
+    lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8;
+    lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hello", "c/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // corrupt the source
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_block_t block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    uint8_t bbuffer[LFS_BLOCK_SIZE];
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    int off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    // corrupt the destination
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    // continue move
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hello", "c/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hello") == 0);
+    assert(info.type == LFS_TYPE_REG);
+    assert(info.size == 5+8+6);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file, buffer, 5) => 5;
+    memcmp(buffer, "hola\n", 5) => 0;
+    lfs_file_read(&lfs, &file, buffer, 8) => 8;
+    memcmp(buffer, "bonjour\n", 8) => 0;
+    lfs_file_read(&lfs, &file, buffer, 6) => 6;
+    memcmp(buffer, "ohayo\n", 6) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # simple reentrant move file
+reentrant = true
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+    err = lfs_mkdir(&lfs, "a");
+    assert(!err || err == LFS_ERR_EXIST);
+    err = lfs_mkdir(&lfs, "b");
+    assert(!err || err == LFS_ERR_EXIST);
+    err = lfs_mkdir(&lfs, "c");
+    assert(!err || err == LFS_ERR_EXIST);
+    err = lfs_mkdir(&lfs, "d");
+    assert(!err || err == LFS_ERR_EXIST);
+    lfs_unmount(&lfs) => 0;
+
+    while (true) {
+        lfs_mount(&lfs, &cfg) => 0;
+        // there should never exist _2_ hello files
+        int count = 0;
+        if (lfs_stat(&lfs, "a/hello", &info) == 0) {
+            assert(strcmp(info.name, "hello") == 0);
+            assert(info.type == LFS_TYPE_REG);
+            assert(info.size == 5+8+6 || info.size == 0);
+            count += 1;
+        }
+        if (lfs_stat(&lfs, "b/hello", &info) == 0) {
+            assert(strcmp(info.name, "hello") == 0);
+            assert(info.type == LFS_TYPE_REG);
+            assert(info.size == 5+8+6);
+            count += 1; 
+        }
+        if (lfs_stat(&lfs, "c/hello", &info) == 0) {
+            assert(strcmp(info.name, "hello") == 0);
+            assert(info.type == LFS_TYPE_REG);
+            assert(info.size == 5+8+6);
+            count += 1;
+        }
+        if (lfs_stat(&lfs, "d/hello", &info) == 0) {
+            assert(strcmp(info.name, "hello") == 0);
+            assert(info.type == LFS_TYPE_REG);
+            assert(info.size == 5+8+6);
+            count += 1;
+        }
+        assert(count <= 1);
+        lfs_unmount(&lfs) => 0;
+
+        lfs_mount(&lfs, &cfg) => 0;
+        if (lfs_stat(&lfs, "a/hello", &info) == 0 && info.size > 0) {
+            lfs_rename(&lfs, "a/hello", "b/hello") => 0;
+        } else if (lfs_stat(&lfs, "b/hello", &info) == 0) {
+            lfs_rename(&lfs, "b/hello", "c/hello") => 0;
+        } else if (lfs_stat(&lfs, "c/hello", &info) == 0) {
+            lfs_rename(&lfs, "c/hello", "d/hello") => 0;
+        } else if (lfs_stat(&lfs, "d/hello", &info) == 0) {
+            // success
+            break;
+        } else {
+            // create file
+            lfs_file_open(&lfs, &file, "a/hello",
+                    LFS_O_WRONLY | LFS_O_CREAT) => 0;
+            lfs_file_write(&lfs, &file, "hola\n", 5) => 5;
+            lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8;
+            lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6;
+            lfs_file_close(&lfs, &file) => 0;
+        }
+        lfs_unmount(&lfs) => 0;
+    }
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "d") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hello") == 0);
+    assert(info.type == LFS_TYPE_REG);
+    assert(info.size == 5+8+6);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file, buffer, 5) => 5;
+    memcmp(buffer, "hola\n", 5) => 0;
+    lfs_file_read(&lfs, &file, buffer, 8) => 8;
+    memcmp(buffer, "bonjour\n", 8) => 0;
+    lfs_file_read(&lfs, &file, buffer, 6) => 6;
+    memcmp(buffer, "ohayo\n", 6) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # move dir
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_mkdir(&lfs, "a/hi") => 0;
+    lfs_mkdir(&lfs, "a/hi/hola") => 0;
+    lfs_mkdir(&lfs, "a/hi/bonjour") => 0;
+    lfs_mkdir(&lfs, "a/hi/ohayo") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hi", "c/hi") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hi") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "c/hi") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "bonjour") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hola") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "ohayo") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "d/hi") => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # move dir corrupt source
+in = "lfs.c"
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_mkdir(&lfs, "a/hi") => 0;
+    lfs_mkdir(&lfs, "a/hi/hola") => 0;
+    lfs_mkdir(&lfs, "a/hi/bonjour") => 0;
+    lfs_mkdir(&lfs, "a/hi/ohayo") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hi", "c/hi") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // corrupt the source
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_block_t block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    uint8_t bbuffer[LFS_BLOCK_SIZE];
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    int off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hi") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "c/hi") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "bonjour") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hola") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "ohayo") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "d/hi") => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # move dir corrupt source and dest
+in = "lfs.c"
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_mkdir(&lfs, "a/hi") => 0;
+    lfs_mkdir(&lfs, "a/hi/hola") => 0;
+    lfs_mkdir(&lfs, "a/hi/bonjour") => 0;
+    lfs_mkdir(&lfs, "a/hi/ohayo") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hi", "c/hi") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // corrupt the source
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_block_t block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    uint8_t bbuffer[LFS_BLOCK_SIZE];
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    int off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    // corrupt the destination
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hi") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_dir_open(&lfs, &dir, "a/hi") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "bonjour") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hola") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "ohayo") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "c/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "d/hi") => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # move dir after corrupt
+in = "lfs.c"
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_mkdir(&lfs, "a/hi") => 0;
+    lfs_mkdir(&lfs, "a/hi/hola") => 0;
+    lfs_mkdir(&lfs, "a/hi/bonjour") => 0;
+    lfs_mkdir(&lfs, "a/hi/ohayo") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hi", "c/hi") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // corrupt the source
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_block_t block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    uint8_t bbuffer[LFS_BLOCK_SIZE];
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    int off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    // corrupt the destination
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    // continue move
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hi", "c/hi") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "c") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hi") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "c/hi") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "bonjour") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hola") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "ohayo") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "d/hi") => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # simple reentrant move dir
+reentrant = true
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+    err = lfs_mkdir(&lfs, "a");
+    assert(!err || err == LFS_ERR_EXIST);
+    err = lfs_mkdir(&lfs, "b");
+    assert(!err || err == LFS_ERR_EXIST);
+    err = lfs_mkdir(&lfs, "c");
+    assert(!err || err == LFS_ERR_EXIST);
+    err = lfs_mkdir(&lfs, "d");
+    assert(!err || err == LFS_ERR_EXIST);
+    lfs_unmount(&lfs) => 0;
+
+    while (true) {
+        lfs_mount(&lfs, &cfg) => 0;
+        // there should never exist _2_ hi directories
+        int count = 0;
+        if (lfs_stat(&lfs, "a/hi", &info) == 0) {
+            assert(strcmp(info.name, "hi") == 0);
+            assert(info.type == LFS_TYPE_DIR);
+            count += 1;
+        }
+        if (lfs_stat(&lfs, "b/hi", &info) == 0) {
+            assert(strcmp(info.name, "hi") == 0);
+            assert(info.type == LFS_TYPE_DIR);
+            count += 1; 
+        }
+        if (lfs_stat(&lfs, "c/hi", &info) == 0) {
+            assert(strcmp(info.name, "hi") == 0);
+            assert(info.type == LFS_TYPE_DIR);
+            count += 1;
+        }
+        if (lfs_stat(&lfs, "d/hi", &info) == 0) {
+            assert(strcmp(info.name, "hi") == 0);
+            assert(info.type == LFS_TYPE_DIR);
+            count += 1;
+        }
+        assert(count <= 1);
+        lfs_unmount(&lfs) => 0;
+
+        lfs_mount(&lfs, &cfg) => 0;
+        if (lfs_stat(&lfs, "a/hi", &info) == 0) {
+            lfs_rename(&lfs, "a/hi", "b/hi") => 0;
+        } else if (lfs_stat(&lfs, "b/hi", &info) == 0) {
+            lfs_rename(&lfs, "b/hi", "c/hi") => 0;
+        } else if (lfs_stat(&lfs, "c/hi", &info) == 0) {
+            lfs_rename(&lfs, "c/hi", "d/hi") => 0;
+        } else if (lfs_stat(&lfs, "d/hi", &info) == 0) {
+            break; // success
+        } else {
+            // create dir and rename for atomicity
+            err = lfs_mkdir(&lfs, "temp");
+            assert(!err || err == LFS_ERR_EXIST);
+            err = lfs_mkdir(&lfs, "temp/hola");
+            assert(!err || err == LFS_ERR_EXIST);
+            err = lfs_mkdir(&lfs, "temp/bonjour");
+            assert(!err || err == LFS_ERR_EXIST);
+            err = lfs_mkdir(&lfs, "temp/ohayo");
+            assert(!err || err == LFS_ERR_EXIST);
+            lfs_rename(&lfs, "temp", "a/hi") => 0;
+        }
+        lfs_unmount(&lfs) => 0;
+    }
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "a") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "d") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hi") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "c/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "d/hi") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "bonjour") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "hola") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "ohayo") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # move state stealing
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "a") => 0;
+    lfs_mkdir(&lfs, "b") => 0;
+    lfs_mkdir(&lfs, "c") => 0;
+    lfs_mkdir(&lfs, "d") => 0;
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hola\n", 5) => 5;
+    lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8;
+    lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "a/hello", "b/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "b/hello", "c/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "c/hello", "d/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file, buffer, 5) => 5;
+    memcmp(buffer, "hola\n", 5) => 0;
+    lfs_file_read(&lfs, &file, buffer, 8) => 8;
+    memcmp(buffer, "bonjour\n", 8) => 0;
+    lfs_file_read(&lfs, &file, buffer, 6) => 6;
+    memcmp(buffer, "ohayo\n", 6) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_remove(&lfs, "b") => 0;
+    lfs_remove(&lfs, "c") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_stat(&lfs, "a", &info) => 0;
+    lfs_stat(&lfs, "b", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "c", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "d", &info) => 0;
+    lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file, buffer, 5) => 5;
+    memcmp(buffer, "hola\n", 5) => 0;
+    lfs_file_read(&lfs, &file, buffer, 8) => 8;
+    memcmp(buffer, "bonjour\n", 8) => 0;
+    lfs_file_read(&lfs, &file, buffer, 6) => 6;
+    memcmp(buffer, "ohayo\n", 6) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''

+ 56 - 0
tests_/test_orphan.toml

@@ -0,0 +1,56 @@
+[[case]] # orphan test
+in = "lfs.c"
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "parent") => 0;
+    lfs_mkdir(&lfs, "parent/orphan") => 0;
+    lfs_mkdir(&lfs, "parent/child") => 0;
+    lfs_remove(&lfs, "parent/orphan") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // corrupt the child's most recent commit, this should be the update
+    // to the linked-list entry, which should orphan the orphan. Note this
+    // makes a lot of assumptions about the remove operation.
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "parent/child") => 0;
+    lfs_block_t block = dir.m.pair[0];
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    uint8_t bbuffer[LFS_BLOCK_SIZE];
+    cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    int off = LFS_BLOCK_SIZE-1;
+    while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) {
+        off -= 1;
+    }
+    memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3);
+    cfg.erase(&cfg, block) => 0;
+    cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg.sync(&cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "parent/child", &info) => 0;
+    lfs_fs_size(&lfs) => 8;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "parent/child", &info) => 0;
+    lfs_fs_size(&lfs) => 8;
+    // this mkdir should both create a dir and deorphan, so size
+    // should be unchanged
+    lfs_mkdir(&lfs, "parent/otherchild") => 0;
+    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "parent/child", &info) => 0;
+    lfs_stat(&lfs, "parent/otherchild", &info) => 0;
+    lfs_fs_size(&lfs) => 8;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "parent/child", &info) => 0;
+    lfs_stat(&lfs, "parent/otherchild", &info) => 0;
+    lfs_fs_size(&lfs) => 8;
+    lfs_unmount(&lfs) => 0;
+'''

+ 24 - 24
tests_/test_seek.toml

@@ -1,5 +1,13 @@
 
 [[case]] # simple file seek
+define = [
+    {COUNT=132, SKIP=4},
+    {COUNT=132, SKIP=128},
+    {COUNT=200, SKIP=10},
+    {COUNT=200, SKIP=100},
+    {COUNT=4,   SKIP=1},
+    {COUNT=4,   SKIP=2},
+]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -59,6 +67,8 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
+
+[[case]] # simple file seek and write
 define = [
     {COUNT=132, SKIP=4},
     {COUNT=132, SKIP=128},
@@ -67,8 +77,6 @@ define = [
     {COUNT=4,   SKIP=1},
     {COUNT=4,   SKIP=2},
 ]
-
-[[case]] # simple file seek and write
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -120,16 +128,10 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define = [
-    {COUNT=132, SKIP=4},
-    {COUNT=132, SKIP=128},
-    {COUNT=200, SKIP=10},
-    {COUNT=200, SKIP=100},
-    {COUNT=4,   SKIP=1},
-    {COUNT=4,   SKIP=2},
-]
 
 [[case]] # boundary seek and writes
+define.COUNT = 132
+define.OFFSETS = '"{512, 1020, 513, 1021, 511, 1019, 1441}"'
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -180,10 +182,16 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.COUNT = 132
-define.OFFSETS = '"{512, 1020, 513, 1021, 511, 1019, 1441}"'
 
 [[case]] # out of bounds seek
+define = [
+    {COUNT=132, SKIP=4},
+    {COUNT=132, SKIP=128},
+    {COUNT=200, SKIP=10},
+    {COUNT=200, SKIP=100},
+    {COUNT=4,   SKIP=2},
+    {COUNT=4,   SKIP=3},
+]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -229,16 +237,9 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define = [
-    {COUNT=132, SKIP=4},
-    {COUNT=132, SKIP=128},
-    {COUNT=200, SKIP=10},
-    {COUNT=200, SKIP=100},
-    {COUNT=4,   SKIP=2},
-    {COUNT=4,   SKIP=3},
-]
 
 [[case]] # inline write and seek
+define.SIZE = [2, 4, 128, 132]
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -303,9 +304,11 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.SIZE = [2, 4, 128, 132]
 
 [[case]] # file seek and write with power-loss
+# must be power-of-2 for quadratic probing to be exhaustive
+define.COUNT = [4, 64, 128]
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -375,6 +378,3 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-# must be power-of-2 for quadratic probing to be exhaustive
-define.COUNT = [4, 64, 128]
-reentrant = true

+ 14 - 15
tests_/test_truncate.toml

@@ -1,4 +1,6 @@
 [[case]] # simple truncate
+define.MEDIUMSIZE = [32, 2048]
+define.LARGESIZE = 8192
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -39,10 +41,10 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.MEDIUMSIZE = [32, 2048]
-define.LARGESIZE = 8192
 
 [[case]] # truncate and read
+define.MEDIUMSIZE = [32, 2048]
+define.LARGESIZE = 8192
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -90,8 +92,6 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.MEDIUMSIZE = [32, 2048]
-define.LARGESIZE = 8192
 
 [[case]] # write, truncate, and read
 code = '''
@@ -146,6 +146,8 @@ code = '''
 '''
 
 [[case]] # truncate and write
+define.MEDIUMSIZE = [32, 2048]
+define.LARGESIZE = 8192
 code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -193,10 +195,12 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
-define.MEDIUMSIZE = [32, 2048]
-define.LARGESIZE = 8192
 
 [[case]] # truncate write under powerloss
+define.SMALLSIZE = [4, 512]
+define.MEDIUMSIZE = [32, 1024]
+define.LARGESIZE = 2048
+reentrant = true
 code = '''
     err = lfs_mount(&lfs, &cfg);
     if (err) {
@@ -257,12 +261,12 @@ code = '''
 
     lfs_unmount(&lfs) => 0;
 '''
-define.SMALLSIZE = [4, 512]
-define.MEDIUMSIZE = [32, 1024]
-define.LARGESIZE = 2048
-reentrant = true
 
 [[case]] # more aggressive general truncation tests
+define.CONFIG = 'range(6)'
+define.SMALLSIZE = 32
+define.MEDIUMSIZE = 2048
+define.LARGESIZE = 8192
 code = '''
     #define COUNT 5
     const struct {
@@ -388,8 +392,3 @@ code = '''
 
     lfs_unmount(&lfs) => 0;
 '''
-define.CONFIG = 'range(6)'
-define.SMALLSIZE = 32
-define.MEDIUMSIZE = 2048
-define.LARGESIZE = 8192
-