浏览代码

Ported tests to new framework

This mostly required names for each test case, declarations of
previously-implicit variables since the new test framework is more
conservative with what it declares (the small extra effort to add
declarations is well worth the simplicity and improved readability),
and tweaks to work with not-really-constant defines.

Also renamed test_ -> test, replacing the old ./scripts/test.py,
unfortunately git seems to have had a hard time with this.
Christopher Haster 3 年之前
父节点
当前提交
0781f50edb

+ 14 - 22
Makefile

@@ -32,7 +32,7 @@ DEP := $(SRC:%.c=$(BUILDDIR)%.d)
 ASM := $(SRC:%.c=$(BUILDDIR)%.s)
 CGI := $(SRC:%.c=$(BUILDDIR)%.ci)
 
-TESTS ?= $(wildcard tests_/*.toml)
+TESTS ?= $(wildcard tests/*.toml)
 TEST_SRC ?= $(SRC) \
 		$(filter-out $(wildcard bd/*.*.c),$(wildcard bd/*.c)) \
 		runners/test_runner.c
@@ -53,10 +53,11 @@ override CFLAGS += -g3
 override CFLAGS += -I.
 override CFLAGS += -std=c99 -Wall -pedantic
 override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef
+override CFLAGS += -ftrack-macro-expansion=0
 
-override TESTFLAGS_ += -b
+override TESTFLAGS += -b
 # forward -j flag
-override TESTFLAGS_ += $(filter -j%,$(MAKEFLAGS))
+override TESTFLAGS += $(filter -j%,$(MAKEFLAGS))
 ifdef VERBOSE
 override TESTFLAGS     += -v
 override CALLSFLAGS    += -v
@@ -65,15 +66,14 @@ override DATAFLAGS     += -v
 override STACKFLAGS    += -v
 override STRUCTSFLAGS  += -v
 override COVERAGEFLAGS += -v
-override TESTFLAGS_     += -v
-override TESTCFLAGS_    += -v
+override TESTFLAGS     += -v
+override TESTCFLAGS    += -v
 endif
 ifdef EXEC
-override TESTFLAGS_ += --exec="$(EXEC)"
+override TESTFLAGS 	   += --exec="$(EXEC)"
 endif
 ifdef COVERAGE
-override TESTFLAGS += --coverage
-override TESTFLAGS_ += --coverage
+override TESTFLAGS     += --coverage
 endif
 ifdef BUILDDIR
 override TESTFLAGS     += --build-dir="$(BUILDDIR:/=)"
@@ -112,23 +112,16 @@ tags:
 calls: $(CGI)
 	./scripts/calls.py $^ $(CALLSFLAGS)
 
-.PHONY: test
-test:
-	./scripts/test.py $(TESTFLAGS)
-.SECONDEXPANSION:
-test%: tests/test$$(firstword $$(subst \#, ,%)).toml
-	./scripts/test.py $@ $(TESTFLAGS)
-
 .PHONY: test_runner
 test_runner: $(BUILDDIR)runners/test_runner
 
-.PHONY: test_
-test_: test_runner
-	./scripts/test_.py --runner=$(BUILDDIR)runners/test_runner $(TESTFLAGS_)
+.PHONY: test
+test: test_runner
+	./scripts/test.py --runner=$(BUILDDIR)runners/test_runner $(TESTFLAGS)
 
 .PHONY: test_list
 test_list: test_runner
-	./scripts/test_.py --runner=$(BUILDDIR)runners/test_runner $(TESTFLAGS_) -l
+	./scripts/test.py --runner=$(BUILDDIR)runners/test_runner $(TESTFLAGS) -l
 
 .PHONY: code
 code: $(OBJ)
@@ -199,10 +192,10 @@ $(BUILDDIR)%.a.c: $(BUILDDIR)%.c
 	./scripts/explode_asserts.py $< -o $@
 
 $(BUILDDIR)%.t.c: %.toml
-	./scripts/test_.py -c $< $(TESTCFLAGS_) -o $@
+	./scripts/test.py -c $< $(TESTCFLAGS) -o $@
 
 $(BUILDDIR)%.t.c: %.c $(TESTS)
-	./scripts/test_.py -c $(TESTS) -s $< $(TESTCFLAGS_) -o $@
+	./scripts/test.py -c $(TESTS) -s $< $(TESTCFLAGS) -o $@
 
 # clean everything
 .PHONY: clean
@@ -215,7 +208,6 @@ clean:
 	rm -f $(CGI)
 	rm -f $(DEP)
 	rm -f $(ASM)
-	rm -f $(BUILDDIR)tests/*.toml.*
 	rm -f $(TEST_TSRC)
 	rm -f $(TEST_TASRC)
 	rm -f $(TEST_TAOBJ)

+ 11 - 11
runners/test_runner.c

@@ -10,17 +10,17 @@
 // test geometries
 struct test_geometry {
     const char *name;
-    uintmax_t defines[TEST_GEOMETRY_DEFINE_COUNT];
+    intmax_t defines[TEST_GEOMETRY_DEFINE_COUNT];
 };
 
 const struct test_geometry test_geometries[TEST_GEOMETRY_COUNT]
         = TEST_GEOMETRIES;
 
 // test define lookup and management
-const uintmax_t *test_override_defines;
-uintmax_t (*const *test_case_defines)(void);
-const uintmax_t *test_geometry_defines;
-const uintmax_t test_default_defines[TEST_PREDEFINE_COUNT]
+const intmax_t *test_override_defines;
+intmax_t (*const *test_case_defines)(void);
+const intmax_t *test_geometry_defines;
+const intmax_t test_default_defines[TEST_PREDEFINE_COUNT]
         = TEST_DEFAULTS;
 
 uint8_t test_override_predefine_map[TEST_PREDEFINE_COUNT];
@@ -37,7 +37,7 @@ const char *const *test_define_names;
 size_t test_define_count;
 
 
-uintmax_t test_predefine(size_t define) {
+intmax_t test_predefine(size_t define) {
     if (test_override_defines
             && test_override_predefine_map[define] != 0xff) {
         return test_override_defines[test_override_predefine_map[define]];
@@ -52,7 +52,7 @@ uintmax_t test_predefine(size_t define) {
     }
 }
 
-uintmax_t test_define(size_t define) {
+intmax_t test_define(size_t define) {
     if (test_override_defines
             && test_override_define_map[define] != 0xff) {
         return test_override_defines[test_override_define_map[define]];
@@ -73,7 +73,7 @@ static void test_define_geometry(const struct test_geometry *geometry) {
 
 static void test_define_overrides(
         const char *const *override_names,
-        const uintmax_t *override_defines,
+        const intmax_t *override_defines,
         size_t override_count) {
     test_override_defines = override_defines;
     test_override_names = override_names;
@@ -583,7 +583,7 @@ int main(int argc, char **argv) {
     void (*op)(void) = run;
 
     static const char **override_names = NULL;
-    static uintmax_t *override_defines = NULL;
+    static intmax_t *override_defines = NULL;
     static size_t override_count = 0;
     static size_t override_cap = 0;
 
@@ -685,10 +685,10 @@ int main(int argc, char **argv) {
                     override_names = realloc(override_names, override_cap
                             * sizeof(const char *));
                     override_defines = realloc(override_defines, override_cap
-                            * sizeof(uintmax_t));
+                            * sizeof(intmax_t));
                 }
 
-                // parse into string key/uintmax_t value, cannibalizing the
+                // parse into string key/intmax_t value, cannibalizing the
                 // arg in the process
                 char *sep = strchr(optarg, '=');
                 char *parsed = NULL;

+ 3 - 3
runners/test_runner.h

@@ -19,7 +19,7 @@ struct test_case {
     test_types_t types;
     size_t permutations;
 
-    uintmax_t (*const *const *defines)(void);
+    intmax_t (*const *const *defines)(void);
 
     bool (*filter)(void);
     void (*run)(struct lfs_config *cfg);
@@ -43,8 +43,8 @@ extern const size_t test_suite_count;
 
 
 // access generated test defines
-uintmax_t test_predefine(size_t define);
-uintmax_t test_define(size_t define);
+intmax_t test_predefine(size_t define);
+intmax_t test_define(size_t define);
 
 // a few preconfigured defines that control how tests run
 #define READ_SIZE           test_predefine(0)

+ 963 - 796
scripts/test.py

@@ -1,860 +1,1027 @@
 #!/usr/bin/env python3
-
-# This script manages littlefs tests, which are configured with
-# .toml files stored in the tests directory.
+#
+# Script to compile and runs tests.
 #
 
-import toml
+import collections as co
+import errno
 import glob
-import re
-import os
-import io
 import itertools as it
-import collections.abc as abc
-import subprocess as sp
-import base64
-import sys
-import copy
-import shlex
+import math as m
+import os
 import pty
-import errno
+import re
+import shlex
+import shutil
 import signal
+import subprocess as sp
+import threading as th
+import time
+import toml
 
-TEST_PATHS = 'tests'
-RULES = """
-# add block devices to sources
-TESTSRC ?= $(SRC) $(wildcard bd/*.c)
-
-define FLATTEN
-%(path)s%%$(subst /,.,$(target)): $(target)
-    ./scripts/explode_asserts.py $$< -o $$@
-endef
-$(foreach target,$(TESTSRC),$(eval $(FLATTEN)))
-
--include %(path)s*.d
-.SECONDARY:
 
-%(path)s.test: %(path)s.test.o \\
-        $(foreach t,$(subst /,.,$(TESTSRC:.c=.o)),%(path)s.$t)
-    $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
+TEST_PATHS = ['tests']
+RUNNER_PATH = './runners/test_runner'
 
-# needed in case builddir is different
-%(path)s%%.o: %(path)s%%.c
-    $(CC) -c -MMD $(CFLAGS) $< -o $@
-"""
-COVERAGE_RULES = """
-%(path)s.test: override CFLAGS += -fprofile-arcs -ftest-coverage
-
-# delete lingering coverage
-%(path)s.test: | %(path)s.info.clean
-.PHONY: %(path)s.info.clean
-%(path)s.info.clean:
-    rm -f %(path)s*.gcda
-
-# accumulate coverage info
-.PHONY: %(path)s.info
-%(path)s.info:
-    $(strip $(LCOV) -c \\
-        $(addprefix -d ,$(wildcard %(path)s*.gcda)) \\
-        --rc 'geninfo_adjust_src_path=$(shell pwd)' \\
-        -o $@)
-    $(LCOV) -e $@ $(addprefix /,$(SRC)) -o $@
-ifdef COVERAGETARGET
-    $(strip $(LCOV) -a $@ \\
-        $(addprefix -a ,$(wildcard $(COVERAGETARGET))) \\
-        -o $(COVERAGETARGET))
-endif
-"""
-GLOBALS = """
-//////////////// AUTOGENERATED TEST ////////////////
-#include "lfs.h"
+SUITE_PROLOGUE = """
+#include "runners/test_runner.h"
 #include "bd/lfs_testbd.h"
 #include <stdio.h>
-extern const char *lfs_testbd_path;
-extern uint32_t lfs_testbd_cycles;
 """
-DEFINES = {
-    '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_PROGERROR',
-}
-PROLOGUE = """
-    // prologue
-    __attribute__((unused)) lfs_t lfs;
-    __attribute__((unused)) lfs_testbd_t bd;
-    __attribute__((unused)) lfs_file_t file;
-    __attribute__((unused)) lfs_dir_t dir;
-    __attribute__((unused)) struct lfs_info info;
-    __attribute__((unused)) char path[1024];
-    __attribute__((unused)) uint8_t buffer[1024];
-    __attribute__((unused)) lfs_size_t size;
-    __attribute__((unused)) int err;
-    
-    __attribute__((unused)) const struct lfs_config cfg = {
-        .context        = &bd,
-        .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,
-        .block_count    = LFS_BLOCK_COUNT,
-        .block_cycles   = LFS_BLOCK_CYCLES,
-        .cache_size     = LFS_CACHE_SIZE,
-        .lookahead_size = LFS_LOOKAHEAD_SIZE,
-    };
-
-    __attribute__((unused)) const struct lfs_testbd_config bdcfg = {
-        .erase_value        = LFS_ERASE_VALUE,
-        .erase_cycles       = LFS_ERASE_CYCLES,
-        .badblock_behavior  = LFS_BADBLOCK_BEHAVIOR,
-        .power_cycles       = lfs_testbd_cycles,
-    };
-
-    lfs_testbd_createcfg(&cfg, lfs_testbd_path, &bdcfg) => 0;
+CASE_PROLOGUE = """
 """
-EPILOGUE = """
-    // epilogue
-    lfs_testbd_destroy(&cfg) => 0;
+CASE_EPILOGUE = """
 """
-PASS = '\033[32m✓\033[0m'
-FAIL = '\033[31m✗\033[0m'
 
-class TestFailure(Exception):
-    def __init__(self, case, returncode=None, stdout=None, assert_=None):
-        self.case = case
-        self.returncode = returncode
-        self.stdout = stdout
-        self.assert_ = assert_
 
-class TestCase:
-    def __init__(self, config, filter=filter,
-            suite=None, caseno=None, lineno=None, **_):
-        self.config = config
-        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.in_ = config.get('in', None)
-
-        self.result = 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)' % (
-                    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' % (
-                    self.suite.name, self.caseno, self.permno)
-        else:
-            return '%s#%d' % (
-                self.suite.name, self.caseno)
-
-    def permute(self, class_=None, defines={}, permno=None, **_):
-        ncase = (class_ or type(self))(self.config)
-        for k, v in self.__dict__.items():
-            setattr(ncase, k, v)
-        ncase.case = self
-        ncase.perms = [ncase]
-        ncase.permno = permno
-        ncase.defines = defines
-        return ncase
-
-    def build(self, f, **_):
-        # prologue
-        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)))
-
-        f.write(PROLOGUE)
-        f.write('\n')
-        f.write(4*' '+'// test case %d\n' % self.caseno)
-        f.write(4*' '+'#line %d "%s"\n' % (self.code_lineno, self.suite.path))
-
-        # test case goes here
-        f.write(self.code)
-
-        # epilogue
-        f.write(EPILOGUE)
-        f.write('}\n')
-
-        for k, v in sorted(self.defines.items()):
-            if k not in self.suite.defines:
-                f.write('#undef %s\n' % k)
-
-    def shouldtest(self, **args):
-        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 args.get('no_internal') and self.in_ is not None:
-            return False
-        elif self.if_ is not None:
-            if_ = self.if_
-            while True:
-                for k, v in sorted(self.defines.items(),
-                        key=lambda x: len(x[0]), reverse=True):
-                    if k in if_:
-                        if_ = if_.replace(k, '(%s)' % v)
-                        break
-                else:
-                    break
-            if_ = (
-                re.sub('(\&\&|\?)', ' and ',
-                re.sub('(\|\||:)', ' or ',
-                re.sub('!(?!=)', ' not ', if_))))
-            return eval(if_)
-        else:
-            return True
-
-    def test(self, exec=[], persist=False, cycles=None,
-            gdb=False, failure=None, disk=None, **args):
-        # build command
-        cmd = exec + ['./%s.test' % self.suite.path,
-            repr(self.caseno), repr(self.permno)]
-
-        # persist disk or keep in RAM for speed?
-        if persist:
-            if not disk:
-                disk = self.suite.path + '.disk'
-            if persist != 'noerase':
-                try:
-                    with open(disk, 'w') as f:
-                        f.truncate(0)
-                    if args.get('verbose'):
-                        print('truncate --size=0', disk)
-                except FileNotFoundError:
-                    pass
-
-            cmd.append(disk)
-
-        # simulate power-loss after n cycles?
-        if cycles:
-            cmd.append(str(cycles))
-
-        # failed? drop into debugger?
-        if gdb and failure:
-            ncmd = ['gdb']
-            if gdb == 'assert':
-                ncmd.extend(['-ex', 'r'])
-                if failure.assert_:
-                    ncmd.extend(['-ex', 'up 2'])
-            elif gdb == 'main':
-                ncmd.extend([
-                    '-ex', 'b %s:%d' % (self.suite.path, self.code_lineno),
-                    '-ex', 'r'])
-            ncmd.extend(['--args'] + cmd)
-
-            if args.get('verbose'):
-                print(' '.join(shlex.quote(c) for c in ncmd))
-            signal.signal(signal.SIGINT, signal.SIG_IGN)
-            sys.exit(sp.call(ncmd))
-
-        # run test case!
-        mpty, spty = pty.openpty()
-        if args.get('verbose'):
-            print(' '.join(shlex.quote(c) for c in cmd))
-        proc = sp.Popen(cmd, stdout=spty, stderr=spty)
-        os.close(spty)
-        mpty = os.fdopen(mpty, 'r', 1)
-        stdout = []
-        assert_ = None
-        try:
-            while True:
-                try:
-                    line = mpty.readline()
-                except OSError as e:
-                    if e.errno == errno.EIO:
-                        break
-                    raise
-                if not line:
-                    break;
-                stdout.append(line)
-                if args.get('verbose'):
-                    sys.stdout.write(line)
-                # intercept asserts
-                m = re.match(
-                    '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
-                    .format('(?:\033\[[\d;]*.| )*', 'assert'),
-                    line)
-                if m and assert_ is None:
-                    try:
-                        with open(m.group(1)) as f:
-                            lineno = int(m.group(2))
-                            line = (next(it.islice(f, lineno-1, None))
-                                .strip('\n'))
-                        assert_ = {
-                            'path': m.group(1),
-                            'line': line,
-                            'lineno': lineno,
-                            'message': m.group(3)}
-                    except:
-                        pass
-        except KeyboardInterrupt:
-            raise TestFailure(self, 1, stdout, None)
-        proc.wait()
-
-        # did we pass?
-        if proc.returncode != 0:
-            raise TestFailure(self, proc.returncode, stdout, assert_)
-        else:
-            return PASS
+def testpath(path):
+    path, *_ = path.split('#', 1)
+    return path
 
-class ValgrindTestCase(TestCase):
-    def __init__(self, config, **args):
-        self.leaky = config.get('leaky', False)
-        super().__init__(config, **args)
+def testsuite(path):
+    suite = testpath(path)
+    suite = os.path.basename(suite)
+    if suite.endswith('.toml'):
+        suite = suite[:-len('.toml')]
+    return suite
 
-    def shouldtest(self, **args):
-        return not self.leaky and super().shouldtest(**args)
+def testcase(path):
+    _, case, *_ = path.split('#', 2)
+    return '%s#%s' % (testsuite(path), case)
 
-    def test(self, exec=[], **args):
-        verbose = args.get('verbose')
-        uninit = (self.defines.get('LFS_ERASE_VALUE', None) == -1)
-        exec = [
-            'valgrind',
-            '--leak-check=full',
-            ] + (['--undef-value-errors=no'] if uninit else []) + [
-            ] + (['--track-origins=yes'] if not uninit else []) + [
-            '--error-exitcode=4',
-            '--error-limit=no',
-            ] + (['--num-callers=1'] if not verbose else []) + [
-            '-q'] + exec
-        return super().test(exec=exec, **args)
-
-class ReentrantTestCase(TestCase):
-    def __init__(self, config, **args):
-        self.reentrant = config.get('reentrant', False)
-        super().__init__(config, **args)
-
-    def shouldtest(self, **args):
-        return self.reentrant and super().shouldtest(**args)
-
-    def test(self, persist=False, gdb=False, failure=None, **args):
-        for cycles in it.count(1):
-            # clear disk first?
-            if cycles == 1 and persist != 'noerase':
-                persist = 'erase'
-            else:
-                persist = 'noerase'
+# TODO move this out in other files
+def openio(path, mode='r'):
+    if path == '-':
+        if 'r' in mode:
+            return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
+        else:
+            return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
+    else:
+        return open(path, mode)
 
-            # exact cycle we should drop into debugger?
-            if gdb and failure and failure.cycleno == cycles:
-                return super().test(gdb=gdb, persist=persist, cycles=cycles,
-                    failure=failure, **args)
+class TestCase:
+    # create a TestCase object from a config
+    def __init__(self, config, args={}):
+        self.name = config.pop('name')
+        self.path = config.pop('path')
+        self.suite = config.pop('suite')
+        self.lineno = config.pop('lineno', None)
+        self.if_ = config.pop('if', None)
+        if isinstance(self.if_, bool):
+            self.if_ = 'true' if self.if_ else 'false'
+        self.code = config.pop('code')
+        self.code_lineno = config.pop('code_lineno', None)
+        self.in_ = config.pop('in',
+            config.pop('suite_in', None))
+
+        self.normal = config.pop('normal',
+            config.pop('suite_normal', True))
+        self.reentrant = config.pop('reentrant',
+            config.pop('suite_reentrant', False))
+
+        # figure out defines and build possible permutations
+        self.defines = set()
+        self.permutations = []
+
+        suite_defines = config.pop('suite_defines', {})
+        if not isinstance(suite_defines, list):
+            suite_defines = [suite_defines]
+        defines = config.pop('defines', {})
+        if not isinstance(defines, list):
+            defines = [defines]
+
+        # build possible permutations
+        for suite_defines_ in suite_defines:
+            self.defines |= suite_defines_.keys()
+            for defines_ in defines:
+                self.defines |= defines_.keys()
+                self.permutations.extend(map(dict, it.product(*(
+                    [(k, v) for v in (vs if isinstance(vs, list) else [vs])]
+                    for k, vs in sorted(
+                        (suite_defines_ | defines_).items())))))
+
+        for k in config.keys():
+            print('\x1b[01;33mwarning:\x1b[m in %s, found unused key %r'
+                % (self.id(), k),
+                file=sys.stderr)
+
+    def id(self):
+        return '%s#%s' % (self.suite, self.name)
 
-            # 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.
-            try:
-                return super().test(persist=persist, cycles=cycles, **args)
-            except TestFailure as nfailure:
-                if nfailure.returncode == 33:
-                    continue
-                else:
-                    nfailure.cycleno = cycles
-                    raise
 
 class TestSuite:
-    def __init__(self, path, classes=[TestCase], defines={},
-            filter=None, **args):
-        self.name = os.path.basename(path)
-        if self.name.endswith('.toml'):
-            self.name = self.name[:-len('.toml')]
-        if args.get('build_dir'):
-            self.toml = path
-            self.path = args['build_dir'] + '/' + path
-        else:
-            self.toml = path
-            self.path = path
-        self.classes = classes
-        self.defines = defines.copy()
-        self.filter = filter
+    # create a TestSuite object from a toml file
+    def __init__(self, path, args={}):
+        self.name = testsuite(path)
+        self.path = testpath(path)
 
-        with open(self.toml) as f:
+        # load toml file and parse test cases
+        with open(self.path) as f:
             # load tests
             config = toml.load(f)
 
             # find line numbers
             f.seek(0)
-            linenos = []
+            case_linenos = []
             code_linenos = []
             for i, line in enumerate(f):
-                if re.match(r'\[\[\s*case\s*\]\]', line):
-                    linenos.append(i+1)
-                if re.match(r'code\s*=\s*(\'\'\'|""")', line):
+                match = re.match(
+                    '(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
+                        '|' '(?P<code>code\s*=)',
+                    line)
+                if match and match.group('case'):
+                    case_linenos.append((i+1, match.group('name')))
+                elif match and match.group('code'):
                     code_linenos.append(i+2)
 
-            code_linenos.reverse()
-
-        # grab global config
-        for k, v in config.get('define', {}).items():
-            if k not in self.defines:
-                self.defines[k] = v
-        self.code = config.get('code', None)
-        if self.code is not None:
-            self.code_lineno = code_linenos.pop()
-
-        # 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()
-            # merge conditions if necessary
-            if 'if' in config and 'if' in case:
-                case['if'] = '(%s) && (%s)' % (config['if'], case['if'])
-            elif 'if' in config:
-                case['if'] = config['if']
-            # initialize test case
-            self.cases.append(TestCase(case, filter=filter,
-                suite=self, caseno=i+1, lineno=lineno, **args))
-
-    def __str__(self):
+            # sort in case toml parsing did not retain order
+            case_linenos.sort()
+
+            cases = config.pop('cases')
+            for (lineno, name), (nlineno, _) in it.zip_longest(
+                    case_linenos, case_linenos[1:],
+                    fillvalue=(float('inf'), None)):
+                code_lineno = min(
+                    (l for l in code_linenos if l >= lineno and l < nlineno),
+                    default=None)
+                cases[name]['lineno'] = lineno
+                cases[name]['code_lineno'] = code_lineno
+
+            self.if_ = config.pop('if', None)
+            if isinstance(self.if_, bool):
+                self.if_ = 'true' if self.if_ else 'false'
+
+            self.code = config.pop('code', None)
+            self.code_lineno = min(
+                (l for l in code_linenos
+                    if not case_linenos or l < case_linenos[0][0]),
+                default=None)
+
+            # a couple of these we just forward to all cases
+            defines = config.pop('defines', {})
+            in_ = config.pop('in', None)
+            normal = config.pop('normal', True)
+            reentrant = config.pop('reentrant', False)
+
+            self.cases = []
+            for name, case in sorted(cases.items(),
+                    key=lambda c: c[1].get('lineno')):
+                self.cases.append(TestCase(config={
+                    'name': name,
+                    'path': path + (':%d' % case['lineno']
+                        if 'lineno' in case else ''),
+                    'suite': self.name,
+                    'suite_defines': defines,
+                    'suite_in': in_,
+                    'suite_normal': normal,
+                    'suite_reentrant': reentrant,
+                    **case}))
+
+            # combine per-case defines
+            self.defines = set.union(*(
+                set(case.defines) for case in self.cases))
+
+            # combine other per-case things
+            self.normal = any(case.normal for case in self.cases)
+            self.reentrant = any(case.reentrant for case in self.cases)
+
+        for k in config.keys():
+            print('\x1b[01;33mwarning:\x1b[m in %s, found unused key %r'
+                % (self.id(), k),
+                file=sys.stderr)
+
+    def id(self):
         return self.name
 
-    def __lt__(self, other):
-        return self.name < other.name
-
-    def permute(self, **args):
-        for case in self.cases:
-            # lets find all parameterized definitions, in one of [args.D,
-            # suite.defines, case.defines, DEFINES]. Note that each of these
-            # can be either a dict of defines, or a list of dicts, expressing
-            # an initial set of permutations.
-            pending = [{}]
-            for inits in [self.defines, case.defines, DEFINES]:
-                if not isinstance(inits, list):
-                    inits = [inits]
-
-                npending = []
-                for init, pinit in it.product(inits, pending):
-                    ninit = pinit.copy()
-                    for k, v in init.items():
-                        if k not in ninit:
-                            try:
-                                ninit[k] = eval(v)
-                            except:
-                                ninit[k] = v
-                    npending.append(ninit)
-
-                pending = npending
-
-            # expand permutations
-            pending = list(reversed(pending))
-            expanded = []
-            while pending:
-                perm = pending.pop()
-                for k, v in sorted(perm.items()):
-                    if not isinstance(v, str) and isinstance(v, abc.Iterable):
-                        for nv in reversed(v):
-                            nperm = perm.copy()
-                            nperm[k] = nv
-                            pending.append(nperm)
-                        break
-                else:
-                    expanded.append(perm)
-
-            # generate permutations
-            case.perms = []
-            for i, (class_, defines) in enumerate(
-                    it.product(self.classes, expanded)):
-                case.perms.append(case.permute(
-                    class_, defines, permno=i+1, **args))
-
-            # also track non-unique defines
-            case.defines = {}
-            for k, v in case.perms[0].defines.items():
-                if all(perm.defines[k] == v for perm in case.perms):
-                    case.defines[k] = v
-
-        # track all perms and non-unique defines
-        self.perms = []
-        for case in self.cases:
-            self.perms.extend(case.perms)
-
-        self.defines = {}
-        for k, v in self.perms[0].defines.items():
-            if all(perm.defines.get(k, None) == v for perm in self.perms):
-                self.defines[k] = v
-
-        return self.perms
-
-    def build(self, **args):
-        # build test files
-        tf = open(self.path + '.test.tc', 'w')
-        tf.write(GLOBALS)
-        if self.code is not None:
-            tf.write('#line %d "%s"\n' % (self.code_lineno, self.path))
-            tf.write(self.code)
-
-        tfs = {None: tf}
-        for case in self.cases:
-            if case.in_ not in tfs:
-                tfs[case.in_] = open(self.path+'.'+
-                    re.sub('(\.c)?$', '.tc', case.in_.replace('/', '.')), '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_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 > 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' % (
-                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.replace(4*' ', '\t') % dict(path=self.path))
-            mk.write('\n')
-
-            # add coverage hooks?
-            if args.get('coverage'):
-                mk.write(COVERAGE_RULES.replace(4*' ', '\t') % dict(
-                    path=self.path))
-                mk.write('\n')
-
-            # add truly global defines globally
-            for k, v in sorted(self.defines.items()):
-                mk.write('%s.test: override CFLAGS += -D%s=%r\n'
-                    % (self.path, k, v))
-
-            for path in tfs:
-                if path is None:
-                    mk.write('%s: %s | %s\n' % (
-                        self.path+'.test.c',
-                        self.toml,
-                        self.path+'.test.tc'))
-                else:
-                    mk.write('%s: %s %s | %s\n' % (
-                        self.path+'.'+path.replace('/', '.'),
-                        self.toml,
-                        path,
-                        self.path+'.'+re.sub('(\.c)?$', '.tc',
-                            path.replace('/', '.'))))
-                mk.write('\t./scripts/explode_asserts.py $| -o $@\n')
-
-        self.makefile = self.path + '.mk'
-        self.target = self.path + '.test'
-        return self.makefile, self.target
-
-    def test(self, **args):
-        # run test suite!
-        if not args.get('verbose', True):
-            sys.stdout.write(self.name + ' ')
-            sys.stdout.flush()
-        for perm in self.perms:
-            if not perm.shouldtest(**args):
-                continue
 
-            try:
-                result = perm.test(**args)
-            except TestFailure as failure:
-                perm.result = failure
-                if not args.get('verbose', True):
-                    sys.stdout.write(FAIL)
-                    sys.stdout.flush()
-                if not args.get('keep_going'):
-                    if not args.get('verbose', True):
-                        sys.stdout.write('\n')
-                    raise
-            else:
-                perm.result = PASS
-                if not args.get('verbose', True):
-                    sys.stdout.write(PASS)
-                    sys.stdout.flush()
 
-        if not args.get('verbose', True):
-            sys.stdout.write('\n')
+def compile(**args):
+    # find .toml files
+    paths = []
+    for path in args.get('test_ids', TEST_PATHS):
+        if os.path.isdir(path):
+            path = path + '/*.toml'
 
-def main(**args):
-    # figure out explicit defines
-    defines = {}
-    for define in args['D']:
-        k, v, *_ = define.split('=', 2) + ['']
-        defines[k] = v
-
-    # and what class of TestCase to run
-    classes = []
-    if args.get('normal'):
-        classes.append(TestCase)
-    if args.get('reentrant'):
-        classes.append(ReentrantTestCase)
+        for path in glob.glob(path):
+            paths.append(path)
+
+    if not paths:
+        print('no test suites found in %r?' % args['test_ids'])
+        sys.exit(-1)
+
+    if not args.get('source'):
+        if len(paths) > 1:
+            print('more than one test suite for compilation? (%r)'
+                % args['test_ids'])
+            sys.exit(-1)
+
+        # load our suite
+        suite = TestSuite(paths[0])
+    else:
+        # load all suites
+        suites = [TestSuite(path) for path in paths]
+        suites.sort(key=lambda s: s.name)
+
+    # write generated test source
+    if 'output' in args:
+        with openio(args['output'], 'w') as f:
+            _write = f.write
+            def write(s):
+                f.lineno += s.count('\n')
+                _write(s)
+            def writeln(s=''):
+                f.lineno += s.count('\n') + 1
+                _write(s)
+                _write('\n')
+            f.lineno = 1
+            f.write = write
+            f.writeln = writeln
+
+            # redirect littlefs tracing
+            f.writeln('#define LFS_TRACE_(fmt, ...) do { \\')
+            f.writeln(8*' '+'extern FILE *test_trace; \\')
+            f.writeln(8*' '+'if (test_trace) { \\')
+            f.writeln(12*' '+'fprintf(test_trace, '
+                '"%s:%d:trace: " fmt "%s\\n", \\')
+            f.writeln(20*' '+'__FILE__, __LINE__, __VA_ARGS__); \\')
+            f.writeln(8*' '+'} \\')
+            f.writeln(4*' '+'} while (0)')
+            f.writeln('#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")')
+            f.writeln('#define LFS_TESTBD_TRACE(...) '
+                'LFS_TRACE_(__VA_ARGS__, "")')
+            f.writeln()
+
+            # write out generated functions, this can end up in different
+            # files depending on the "in" attribute
+            #
+            # note it's up to the specific generated file to declare
+            # the test defines
+            def write_case_functions(f, suite, case):
+                    # create case define functions
+                    if case.defines:
+                        # deduplicate defines by value to try to reduce the
+                        # number of functions we generate
+                        define_cbs = {}
+                        for i, defines in enumerate(case.permutations):
+                            for k, v in sorted(defines.items()):
+                                if v not in define_cbs:
+                                    name = ('__test__%s__%s__%s__%d'
+                                        % (suite.name, case.name, k, i))
+                                    define_cbs[v] = name
+                                    f.writeln('intmax_t %s(void) {' % name)
+                                    f.writeln(4*' '+'return %s;' % v)
+                                    f.writeln('}')
+                                    f.writeln()
+                        f.writeln('intmax_t (*const *const '
+                            '__test__%s__%s__defines[])(void) = {'
+                            % (suite.name, case.name))
+                        for defines in case.permutations:
+                            f.writeln(4*' '+'(intmax_t (*const[])(void)){')
+                            for define in sorted(suite.defines):
+                                f.writeln(8*' '+'%s,' % (
+                                    define_cbs[defines[define]]
+                                        if define in defines
+                                        else 'NULL'))
+                            f.writeln(4*' '+'},')
+                        f.writeln('};')
+                        f.writeln()    
+
+                    # create case filter function
+                    if suite.if_ is not None or case.if_ is not None:
+                        f.writeln('bool __test__%s__%s__filter(void) {'
+                            % (suite.name, case.name))
+                        f.writeln(4*' '+'return %s;'
+                            % ' && '.join('(%s)' % if_
+                                for if_ in [suite.if_, case.if_]
+                                if if_ is not None))
+                        f.writeln('}')
+                        f.writeln()
+
+                    # create case run function
+                    f.writeln('void __test__%s__%s__run('
+                        '__attribute__((unused)) struct lfs_config *cfg) {'
+                        % (suite.name, case.name))
+                    if CASE_PROLOGUE.strip():
+                        f.writeln(4*' '+'%s'
+                            % CASE_PROLOGUE.strip().replace('\n', '\n'+4*' '))
+                        f.writeln()
+                    f.writeln(4*' '+'// test case %s' % case.id())
+                    if case.code_lineno is not None:
+                        f.writeln(4*' '+'#line %d "%s"'
+                            % (case.code_lineno, suite.path))
+                    f.write(case.code)
+                    if case.code_lineno is not None:
+                        f.writeln(4*' '+'#line %d "%s"'
+                            % (f.lineno+1, args['output']))
+                    if CASE_EPILOGUE.strip():
+                        f.writeln()
+                        f.writeln(4*' '+'%s'
+                            % CASE_EPILOGUE.strip().replace('\n', '\n'+4*' '))
+                    f.writeln('}')
+                    f.writeln()
+
+            if not args.get('source'):
+                # write test suite prologue
+                f.writeln('%s' % SUITE_PROLOGUE.strip())
+                f.writeln()
+                if suite.code is not None:
+                    if suite.code_lineno is not None:
+                        f.writeln('#line %d "%s"'
+                            % (suite.code_lineno, suite.path))
+                    f.write(suite.code)
+                    if suite.code_lineno is not None:
+                        f.writeln('#line %d "%s"'
+                            % (f.lineno+1, args['output']))
+                    f.writeln()
+
+                if suite.defines:
+                    for i, define in enumerate(sorted(suite.defines)):
+                        f.writeln('#ifndef %s' % define)
+                        f.writeln('#define %-24s test_define(%d)'
+                            % (define, i))
+                        f.writeln('#endif')
+                    f.writeln()
+
+                for case in suite.cases:
+                    # create case functions
+                    if case.in_ is None:
+                        write_case_functions(f, suite, case)
+                    else:
+                        if case.defines:
+                            f.writeln('extern intmax_t (*const *const '
+                                '__test__%s__%s__defines[])(void);'
+                                % (suite.name, case.name))
+                        if suite.if_ is not None or case.if_ is not None:
+                            f.writeln('extern bool __test__%s__%s__filter('
+                                'void);'
+                                % (suite.name, case.name))
+                        f.writeln('extern void __test__%s__%s__run('
+                            'struct lfs_config *cfg);'
+                            % (suite.name, case.name))
+                        f.writeln()
+
+                    # create case struct
+                    f.writeln('const struct test_case __test__%s__%s__case = {'
+                        % (suite.name, case.name))
+                    f.writeln(4*' '+'.id = "%s",' % case.id())
+                    f.writeln(4*' '+'.name = "%s",' % case.name)
+                    f.writeln(4*' '+'.path = "%s",' % case.path)
+                    f.writeln(4*' '+'.types = %s,'
+                        % ' | '.join(filter(None, [
+                            'TEST_NORMAL' if case.normal else None,
+                            'TEST_REENTRANT' if case.reentrant else None])))
+                    f.writeln(4*' '+'.permutations = %d,'
+                        % len(case.permutations))
+                    if case.defines:
+                        f.writeln(4*' '+'.defines = __test__%s__%s__defines,'
+                            % (suite.name, case.name))
+                    if suite.if_ is not None or case.if_ is not None:
+                        f.writeln(4*' '+'.filter = __test__%s__%s__filter,'
+                            % (suite.name, case.name))
+                    f.writeln(4*' '+'.run = __test__%s__%s__run,'
+                        % (suite.name, case.name))
+                    f.writeln('};')
+                    f.writeln()
+
+                # create suite define names
+                if suite.defines:
+                    f.writeln('const char *const __test__%s__define_names[] = {'
+                        % suite.name)
+                    for k in sorted(suite.defines):
+                        f.writeln(4*' '+'"%s",' % k)
+                    f.writeln('};')
+                    f.writeln()
+
+                # create suite struct
+                f.writeln('const struct test_suite __test__%s__suite = {'
+                    % suite.name)
+                f.writeln(4*' '+'.id = "%s",' % suite.id())
+                f.writeln(4*' '+'.name = "%s",' % suite.name)
+                f.writeln(4*' '+'.path = "%s",' % suite.path)
+                f.writeln(4*' '+'.types = %s,'
+                    % ' | '.join(filter(None, [
+                        'TEST_NORMAL' if suite.normal else None,
+                        'TEST_REENTRANT' if suite.reentrant else None])))
+                if suite.defines:
+                    f.writeln(4*' '+'.define_names = __test__%s__define_names,'
+                        % suite.name)
+                f.writeln(4*' '+'.define_count = %d,' % len(suite.defines))
+                f.writeln(4*' '+'.cases = (const struct test_case *const []){')
+                for case in suite.cases:
+                    f.writeln(8*' '+'&__test__%s__%s__case,'
+                        % (suite.name, case.name))
+                f.writeln(4*' '+'},')
+                f.writeln(4*' '+'.case_count = %d,' % len(suite.cases))
+                f.writeln('};')
+                f.writeln()
+
+            else:
+                # copy source
+                f.writeln('#line 1 "%s"' % args['source'])
+                with open(args['source']) as sf:
+                    shutil.copyfileobj(sf, f)
+                f.writeln()
+
+                f.write(SUITE_PROLOGUE)
+                f.writeln()
+
+                # write any internal tests
+                for suite in suites:
+                    for case in suite.cases:
+                        if (case.in_ is not None
+                                and os.path.normpath(case.in_)
+                                    == os.path.normpath(args['source'])):
+                            # write defines, but note we need to undef any
+                            # new defines since we're in someone else's file
+                            if suite.defines:
+                                for i, define in enumerate(
+                                        sorted(suite.defines)):
+                                    f.writeln('#ifndef %s' % define)
+                                    f.writeln('#define %-24s test_define(%d)'
+                                        % (define, i))
+                                    f.writeln('#define __TEST__%s__NEEDS_UNDEF'
+                                        % define)
+                                    f.writeln('#endif')
+                                f.writeln()
+
+                            write_case_functions(f, suite, case)
+
+                            if suite.defines:
+                                for define in sorted(suite.defines):
+                                    f.writeln('#ifdef __TEST__%s__NEEDS_UNDEF'
+                                        % define)
+                                    f.writeln('#undef __TEST__%s__NEEDS_UNDEF'
+                                        % define)
+                                    f.writeln('#undef %s' % define)
+                                    f.writeln('#endif')
+                                f.writeln()
+
+                # add suite info to test_runner.c
+                if args['source'] == 'runners/test_runner.c':
+                    f.writeln()
+                    for suite in suites:
+                        f.writeln('extern const struct test_suite '
+                            '__test__%s__suite;' % suite.name)
+                    f.writeln('const struct test_suite *test_suites[] = {')
+                    for suite in suites:
+                        f.writeln(4*' '+'&__test__%s__suite,' % suite.name)
+                    f.writeln('};')
+                    f.writeln('const size_t test_suite_count = %d;'
+                        % len(suites))
+
+def runner(**args):
+    cmd = args['runner'].copy()
+    cmd.extend(args.get('test_ids'))
+
+    # run under some external command?
+    cmd[:0] = args.get('exec', [])
+
+    # run under valgrind?
     if args.get('valgrind'):
-        classes.append(ValgrindTestCase)
-    if not classes:
-        classes = [TestCase]
-
-    suites = []
-    for testpath in args['test_paths']:
-        # 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 + '/*.toml'
-        elif os.path.isfile(testpath):
-            testpath = testpath
-        elif testpath.endswith('.toml'):
-            testpath = TEST_PATHS + '/' + testpath
-        else:
-            testpath = TEST_PATHS + '/' + testpath + '.toml'
-
-        # find tests
-        for path in glob.glob(testpath):
-            suites.append(TestSuite(path, classes, defines, filter, **args))
-
-    # sort for reproducibility
-    suites = sorted(suites)
-
-    # generate permutations
-    for suite in suites:
-        suite.permute(**args)
-
-    # build tests in parallel
-    print('====== building ======')
-    makefiles = []
-    targets = []
-    for suite in suites:
-        makefile, target = suite.build(**args)
-        makefiles.append(makefile)
-        targets.append(target)
-
-    cmd = (['make', '-f', 'Makefile'] +
-        list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
-        [target for target in targets])
-    mpty, spty = pty.openpty()
+        cmd[:0] = filter(None, [
+            'valgrind',
+            '--leak-check=full',
+            '--track-origins=yes',
+            '--error-exitcode=4',
+            '-q'])
+
+    # filter tests?
+    if args.get('normal'):    cmd.append('-n')
+    if args.get('reentrant'): cmd.append('-r')
+    if args.get('geometry'):
+        cmd.append('-G%s' % args.get('geometry'))
+
+    # defines?
+    if args.get('define'):
+        for define in args.get('define'):
+            cmd.append('-D%s' % define)
+
+    return cmd
+
+def list_(**args):
+    cmd = runner(**args)
+    if args.get('summary'):         cmd.append('--summary')
+    if args.get('list_suites'):     cmd.append('--list-suites')
+    if args.get('list_cases'):      cmd.append('--list-cases')
+    if args.get('list_paths'):      cmd.append('--list-paths')
+    if args.get('list_defines'):    cmd.append('--list-defines')
+    if args.get('list_geometries'): cmd.append('--list-geometries')
+
     if args.get('verbose'):
         print(' '.join(shlex.quote(c) for c in cmd))
-    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
-        if not line:
-            break;
-        stdout.append(line)
-        if args.get('verbose'):
-            sys.stdout.write(line)
-        # intercept warnings
-        m = re.match(
-            '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
-            .format('(?:\033\[[\d;]*.| )*', 'warning'),
-            line)
-        if m and not args.get('verbose'):
-            try:
-                with open(m.group(1)) as f:
-                    lineno = int(m.group(2))
-                    line = next(it.islice(f, lineno-1, None)).strip('\n')
-                sys.stdout.write(
-                    "\033[01m{path}:{lineno}:\033[01;35mwarning:\033[m "
-                    "{message}\n{line}\n\n".format(
-                        path=m.group(1), line=line, lineno=lineno,
-                        message=m.group(3)))
-            except:
-                pass
+    sys.exit(sp.call(cmd))
+
+
+def find_cases(runner_, **args):
+    # query from runner
+    cmd = runner_ + ['--list-cases']
+    if args.get('verbose'):
+        print(' '.join(shlex.quote(c) for c in cmd))
+    proc = sp.Popen(cmd,
+        stdout=sp.PIPE,
+        stderr=sp.PIPE if not args.get('verbose') else None,
+        universal_newlines=True,
+        errors='replace')
+    expected_suite_perms = co.defaultdict(lambda: 0)
+    expected_case_perms = co.defaultdict(lambda: 0)
+    expected_perms = 0
+    total_perms = 0
+    pattern = re.compile(
+        '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
+            '[^\s]+\s+(?P<filtered>\d+)/(?P<perms>\d+)')
+    # skip the first line
+    for line in it.islice(proc.stdout, 1, None):
+        m = pattern.match(line)
+        if m:
+            filtered = int(m.group('filtered'))
+            expected_suite_perms[m.group('suite')] += filtered
+            expected_case_perms[m.group('id')] += filtered
+            expected_perms += filtered
+            total_perms += int(m.group('perms'))
     proc.wait()
     if proc.returncode != 0:
         if not args.get('verbose'):
-            for line in stdout:
+            for line in proc.stderr:
                 sys.stdout.write(line)
         sys.exit(-1)
 
-    print('built %d test suites, %d test cases, %d permutations' % (
-        len(suites),
-        sum(len(suite.cases) for suite in suites),
-        sum(len(suite.perms) for suite in suites)))
+    return (
+        expected_suite_perms,
+        expected_case_perms,
+        expected_perms,
+        total_perms)
 
-    total = 0
-    for suite in suites:
-        for perm in suite.perms:
-            total += perm.shouldtest(**args)
-    if total != sum(len(suite.perms) for suite in suites):
-        print('filtered down to %d permutations' % total)
+def find_paths(runner_, **args):
+    # query from runner
+    cmd = runner_ + ['--list-paths']
+    if args.get('verbose'):
+        print(' '.join(shlex.quote(c) for c in cmd))
+    proc = sp.Popen(cmd,
+        stdout=sp.PIPE,
+        stderr=sp.PIPE if not args.get('verbose') else None,
+        universal_newlines=True,
+        errors='replace')
+    paths = co.OrderedDict()
+    pattern = re.compile(
+        '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
+            '(?P<path>[^:]+):(?P<lineno>\d+)')
+    for line in proc.stdout:
+        m = pattern.match(line)
+        if m:
+            paths[m.group('id')] = (m.group('path'), int(m.group('lineno')))
+    proc.wait()
+    if proc.returncode != 0:
+        if not args.get('verbose'):
+            for line in proc.stderr:
+                sys.stdout.write(line)
+        sys.exit(-1)
 
-    # only requested to build?
-    if args.get('build'):
-        return 0
+    return paths
 
-    print('====== testing ======')
-    try:
-        for suite in suites:
-            suite.test(**args)
-    except TestFailure:
-        pass
+def find_defines(runner_, **args):
+    # query from runner
+    cmd = runner_ + ['--list-defines']
+    if args.get('verbose'):
+        print(' '.join(shlex.quote(c) for c in cmd))
+    proc = sp.Popen(cmd,
+        stdout=sp.PIPE,
+        stderr=sp.PIPE if not args.get('verbose') else None,
+        universal_newlines=True,
+        errors='replace')
+    defines = co.OrderedDict()
+    pattern = re.compile(
+        '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
+            '(?P<defines>(?:\w+=\w+\s*)+)')
+    for line in proc.stdout:
+        m = pattern.match(line)
+        if m:
+            defines[m.group('id')] = {k: v
+                for k, v in re.findall('(\w+)=(\w+)', m.group('defines'))}
+    proc.wait()
+    if proc.returncode != 0:
+        if not args.get('verbose'):
+            for line in proc.stderr:
+                sys.stdout.write(line)
+        sys.exit(-1)
 
-    print('====== results ======')
-    passed = 0
-    failed = 0
-    for suite in suites:
-        for perm in suite.perms:
-            if perm.result == PASS:
-                passed += 1
-            elif isinstance(perm.result, TestFailure):
-                sys.stdout.write(
-                    "\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m "
-                    "{perm} failed\n".format(
-                        perm=perm, path=perm.suite.path, lineno=perm.lineno,
-                        returncode=perm.result.returncode or 0))
-                if perm.result.stdout:
-                    if perm.result.assert_:
-                        stdout = perm.result.stdout[:-1]
-                    else:
-                        stdout = perm.result.stdout
-                    for line in stdout[-5:]:
-                        sys.stdout.write(line)
-                if perm.result.assert_:
-                    sys.stdout.write(
-                        "\033[01m{path}:{lineno}:\033[01;31massert:\033[m "
-                        "{message}\n{line}\n".format(
-                            **perm.result.assert_))
-                sys.stdout.write('\n')
-                failed += 1
-
-    if args.get('coverage'):
-        # collect coverage info
-        # why -j1? lcov doesn't work in parallel because of gcov limitations
-        cmd = (['make', '-j1', '-f', 'Makefile'] +
-            list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
-            (['COVERAGETARGET=%s' % args['coverage']]
-                if isinstance(args['coverage'], str) else []) +
-            [suite.path + '.info' for suite in suites
-                if any(perm.result == PASS for perm in suite.perms)])
+    return defines
+
+
+class TestFailure(Exception):
+    def __init__(self, id, returncode, output, assert_=None):
+        self.id = id
+        self.returncode = returncode
+        self.output = output
+        self.assert_ = assert_
+
+def run_stage(name, runner_, **args):
+    # get expected suite/case/perm counts
+    expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
+        find_cases(runner_, **args))
+
+    passed_suite_perms = co.defaultdict(lambda: 0)
+    passed_case_perms = co.defaultdict(lambda: 0)
+    passed_perms = 0
+    failures = []
+    killed = False
+
+    pattern = re.compile('^(?:'
+            '(?P<op>running|finished|skipped) '
+                '(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)'
+            '|' '(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
+                ' *(?P<message>.*)' ')$')
+    locals = th.local()
+    children = set()
+
+    def run_runner(runner_):
+        nonlocal passed_suite_perms
+        nonlocal passed_case_perms
+        nonlocal passed_perms
+        nonlocal locals
+
+        # run the tests!
+        cmd = runner_.copy()
+        if args.get('disk'):
+            cmd.append('--disk=%s' % args['disk'])
+        if args.get('trace'):
+            cmd.append('--trace=%s' % args['trace'])
         if args.get('verbose'):
             print(' '.join(shlex.quote(c) for c in cmd))
-        proc = sp.Popen(cmd,
-            stdout=sp.PIPE if not args.get('verbose') else None,
-            stderr=sp.STDOUT if not args.get('verbose') else None,
-            universal_newlines=True)
-        stdout = []
-        for line in proc.stdout:
-            stdout.append(line)
+        mpty, spty = pty.openpty()
+        proc = sp.Popen(cmd, stdout=spty, stderr=spty)
+        os.close(spty)
+        children.add(proc)
+        mpty = os.fdopen(mpty, 'r', 1)
+        if args.get('output'):
+            output = openio(args['output'], 'w')
+
+        last_id = None
+        last_output = []
+        last_assert = None
+        try:
+            while True:
+                # parse a line for state changes
+                try:
+                    line = mpty.readline()
+                except OSError as e:
+                    if e.errno == errno.EIO:
+                        break
+                    raise
+                if not line:
+                    break
+                last_output.append(line)
+                if args.get('output'):
+                    output.write(line)
+                elif args.get('verbose'):
+                    sys.stdout.write(line)
+
+                m = pattern.match(line)
+                if m:
+                    op = m.group('op') or m.group('op_')
+                    if op == 'running':
+                        locals.seen_perms += 1
+                        last_id = m.group('id')
+                        last_output = []
+                        last_assert = None
+                    elif op == 'finished':
+                        passed_suite_perms[m.group('suite')] += 1
+                        passed_case_perms[m.group('case')] += 1
+                        passed_perms += 1
+                    elif op == 'skipped':
+                        locals.seen_perms += 1
+                    elif op == 'assert':
+                        last_assert = (
+                            m.group('path'),
+                            int(m.group('lineno')),
+                            m.group('message'))
+                        # go ahead and kill the process, aborting takes a while
+                        if args.get('keep_going'):
+                            proc.kill()
+        except KeyboardInterrupt:
+            raise TestFailure(last_id, 1, last_output)
+        finally:
+            children.remove(proc)
+            mpty.close()
+            if args.get('output'):
+                output.close()
+
         proc.wait()
         if proc.returncode != 0:
+            raise TestFailure(
+                last_id,
+                proc.returncode,
+                last_output,
+                last_assert)
+
+    def run_job(runner, start=None, step=None):
+        nonlocal failures
+        nonlocal locals
+
+        start = start or 0
+        step = step or 1
+        while start < total_perms:
+            runner_ = runner.copy()
+            if start is not None:
+                runner_.append('--start=%d' % start)
+            if step is not None:
+                runner_.append('--step=%d' % step)
+            if args.get('isolate') or args.get('valgrind'):
+                runner_.append('--stop=%d' % (start+step))
+
+            try:
+                # run the tests
+                locals.seen_perms = 0
+                run_runner(runner_)
+                assert locals.seen_perms > 0
+                start += locals.seen_perms*step
+
+            except TestFailure as failure:
+                # race condition for multiple failures?
+                if failures and not args.get('keep_going'):
+                    break
+
+                failures.append(failure)
+
+                if args.get('keep_going') and not killed:
+                    # resume after failed test
+                    assert locals.seen_perms > 0
+                    start += locals.seen_perms*step
+                    continue
+                else:
+                    # stop other tests
+                    for child in children.copy():
+                        child.kill()
+    
+
+    # parallel jobs?
+    runners = []
+    if 'jobs' in args:
+        for job in range(args['jobs']):
+            runners.append(th.Thread(
+                target=run_job, args=(runner_, job, args['jobs'])))
+    else:
+        runners.append(th.Thread(
+            target=run_job, args=(runner_, None, None)))
+
+    for r in runners:
+        r.start()
+
+    needs_newline = False
+    try:
+        while any(r.is_alive() for r in runners):
+            time.sleep(0.01)
+
             if not args.get('verbose'):
-                for line in stdout:
-                    sys.stdout.write(line)
-            sys.exit(-1)
+                sys.stdout.write('\r\x1b[K'
+                    'running \x1b[%dm%s:\x1b[m %s '
+                    % (32 if not failures else 31,
+                        name,
+                        ', '.join(filter(None, [
+                            '%d/%d suites' % (
+                                sum(passed_suite_perms[k] == v
+                                    for k, v in expected_suite_perms.items()),
+                                len(expected_suite_perms))
+                                if (not args.get('by_suites')
+                                    and not args.get('by_cases')) else None,
+                            '%d/%d cases' % (
+                                sum(passed_case_perms[k] == v
+                                    for k, v in expected_case_perms.items()),
+                                len(expected_case_perms))
+                                if not args.get('by_cases') else None,
+                            '%d/%d perms' % (passed_perms, expected_perms),
+                            '\x1b[31m%d/%d failures\x1b[m'
+                                % (len(failures), expected_perms)
+                                if failures else None]))))
+                sys.stdout.flush()
+                needs_newline = True
+    except KeyboardInterrupt:
+        # this is handled by the runner threads, we just
+        # need to not abort here
+        killed = True
+    finally:
+        if needs_newline:
+            print()
+
+    for r in runners:
+        r.join()
+
+    return (
+        expected_perms,
+        passed_perms,
+        failures,
+        killed)
+    
+
+def run(**args):
+    start = time.time()
+
+    runner_ = runner(**args)
+    print('using runner: %s'
+        % ' '.join(shlex.quote(c) for c in runner_))
+    expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
+        find_cases(runner_, **args))
+    print('found %d suites, %d cases, %d/%d permutations'
+        % (len(expected_suite_perms),
+            len(expected_case_perms),
+            expected_perms,
+            total_perms))
+    print()
+
+    expected = 0
+    passed = 0
+    failures = []
+    for type, by in it.product(
+            ['normal', 'reentrant'],
+            expected_case_perms.keys() if args.get('by_cases')
+                else expected_suite_perms.keys() if args.get('by_suites')
+                else [None]):
+        # rebuild runner for each stage to override test identifier if needed
+        stage_runner = runner(**args | {
+            'test_ids': [by] if by is not None else args.get('test_ids', []),
+            'normal': type == 'normal',
+            'reentrant': type == 'reentrant'})
+
+        # spawn jobs for stage
+        expected_, passed_, failures_, killed = run_stage(
+            '%s %s' % (type, by or 'tests'), stage_runner, **args)
+        expected += expected_
+        passed += passed_
+        failures.extend(failures_)
+        if (failures and not args.get('keep_going')) or killed:
+            break
+
+    # show summary
+    print()
+    print('\x1b[%dmdone:\x1b[m %d/%d passed, %d/%d failed, in %.2fs'
+        % (32 if not failures else 31,
+            passed, expected, len(failures), expected,
+            time.time()-start))
+    print()
+
+    # print each failure
+    if failures:
+        # get some extra info from runner
+        runner_paths = find_paths(runner_, **args)
+        runner_defines = find_defines(runner_, **args)
+
+    for failure in failures:
+        # show summary of failure
+        path, lineno = runner_paths[testcase(failure.id)]
+        defines = runner_defines[failure.id]
+
+        print('\x1b[01m%s:%d:\x1b[01;31mfailure:\x1b[m %s%s failed'
+            % (path, lineno, failure.id,
+                ' (%s)' % ', '.join(
+                    '%s=%s' % (k, v) for k, v in defines.items())
+                if defines else ''))
+
+        if failure.output:
+            output = failure.output
+            if failure.assert_ is not None:
+                output = output[:-1]
+            for line in output[-5:]:
+                sys.stdout.write(line)
+
+        if failure.assert_ is not None:
+            path, lineno, message = failure.assert_
+            print('\x1b[01m%s:%d:\x1b[01;31massert:\x1b[m %s'
+                % (path, lineno, message))
+            with open(path) as f:
+                line = next(it.islice(f, lineno-1, None)).strip('\n')
+                print(line)
+        print()
+
+    # drop into gdb?
+    if failures and (args.get('gdb')
+            or args.get('gdb_case')
+            or args.get('gdb_main')):
+        failure = failures[0]
+        runner_ = runner(**args | {'test_ids': [failure.id]})
+
+        if args.get('gdb_main'):
+            cmd = ['gdb',
+                '-ex', 'break main',
+                '-ex', 'run',
+                '--args'] + runner_
+        elif args.get('gdb_case'):
+            path, lineno = runner_paths[testcase(failure.id)]
+            cmd = ['gdb',
+                '-ex', 'break %s:%d' % (path, lineno),
+                '-ex', 'run',
+                '--args'] + runner_
+        elif failure.assert_ is not None:
+            cmd = ['gdb',
+                '-ex', 'run',
+                '-ex', 'frame function raise',
+                '-ex', 'up 2',
+                '--args'] + runner_
+        else:
+            cmd = ['gdb',
+                '-ex', 'run',
+                '--args'] + runner_
+
+        # exec gdb interactively
+        if args.get('verbose'):
+            print(' '.join(shlex.quote(c) for c in cmd))
+        os.execvp(cmd[0], cmd)
+
+    return 1 if failures else 0
+
+
+def main(**args):
+    if args.get('compile'):
+        compile(**args)
+    elif (args.get('summary')
+            or args.get('list_suites')
+            or args.get('list_cases')
+            or args.get('list_paths')
+            or args.get('list_defines')
+            or args.get('list_geometries')
+            or args.get('list_defaults')):
+        list_(**args)
+    else:
+        run(**args)
 
-    if args.get('gdb'):
-        failure = None
-        for suite in suites:
-            for perm in suite.perms:
-                if isinstance(perm.result, TestFailure):
-                    failure = perm.result
-        if failure is not None:
-            print('======= gdb ======')
-            # drop into gdb
-            failure.case.test(failure=failure, **args)
-            sys.exit(0)
-
-    print('tests passed %d/%d (%.1f%%)' % (passed, total,
-        100*(passed/total if total else 1.0)))
-    print('tests failed %d/%d (%.1f%%)' % (failed, total,
-        100*(failed/total if total else 1.0)))
-    return 1 if failed > 0 else 0
 
 if __name__ == "__main__":
     import argparse
+    import sys
     parser = argparse.ArgumentParser(
-        description="Run parameterized tests in various configurations.")
-    parser.add_argument('test_paths', nargs='*', default=[TEST_PATHS],
-        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 \
-            specific test cases and permutations. For example \
-            \"test_dirs#1\" or \"{0}/test_dirs.toml#1#1\".".format(TEST_PATHS))
-    parser.add_argument('-D', action='append', default=[],
-        help="Overriding parameter definitions.")
+        description="Build and run tests.",
+        conflict_handler='resolve')
+    parser.add_argument('test_ids', nargs='*',
+        help="Description of testis to run. May be a directory, path, or \
+            test identifier. Test identifiers are of the form \
+            <suite_name>#<case_name>#<permutation>, but suffixes can be \
+            dropped to run any matching tests. Defaults to %r." % TEST_PATHS)
     parser.add_argument('-v', '--verbose', action='store_true',
-        help="Output everything that is happening.")
-    parser.add_argument('-k', '--keep-going', action='store_true',
-        help="Run all tests instead of stopping on first error. Useful for CI.")
-    parser.add_argument('-p', '--persist', choices=['erase', 'noerase'],
-        nargs='?', const='erase',
-        help="Store disk image in a file.")
-    parser.add_argument('-b', '--build', action='store_true',
-        help="Only build the tests, do not execute.")
-    parser.add_argument('-g', '--gdb', choices=['init', 'main', 'assert'],
-        nargs='?', const='assert',
+        help="Output commands that run behind the scenes.")
+    # test flags
+    test_parser = parser.add_argument_group('test options')
+    test_parser.add_argument('-Y', '--summary', action='store_true',
+        help="Show quick summary.")
+    test_parser.add_argument('-l', '--list-suites', action='store_true',
+        help="List test suites.")
+    test_parser.add_argument('-L', '--list-cases', action='store_true',
+        help="List test cases.")
+    test_parser.add_argument('--list-paths', action='store_true',
+        help="List the path for each test case.")
+    test_parser.add_argument('--list-defines', action='store_true',
+        help="List the defines for each test permutation.")
+    test_parser.add_argument('--list-geometries', action='store_true',
+        help="List the disk geometries used for testing.")
+    test_parser.add_argument('--list-defaults', action='store_true',
+        help="List the default defines in this test-runner.")
+    test_parser.add_argument('-D', '--define', action='append',
+        help="Override a test define.")
+    test_parser.add_argument('-G', '--geometry',
+        help="Filter by geometry.")
+    test_parser.add_argument('-n', '--normal', action='store_true',
+        help="Filter for normal tests. Can be combined.")
+    test_parser.add_argument('-r', '--reentrant', action='store_true',
+        help="Filter for reentrant tests. Can be combined.")
+    test_parser.add_argument('-d', '--disk',
+        help="Use this file as the disk.")
+    test_parser.add_argument('-t', '--trace',
+        help="Redirect trace output to this file.")
+    test_parser.add_argument('-o', '--output',
+        help="Redirect stdout and stderr to this file.")
+    test_parser.add_argument('--runner', default=[RUNNER_PATH],
+        type=lambda x: x.split(),
+        help="Path to runner, defaults to %r" % RUNNER_PATH)
+    test_parser.add_argument('-j', '--jobs', nargs='?', type=int,
+        const=len(os.sched_getaffinity(0)),
+        help="Number of parallel runners to run.")
+    test_parser.add_argument('-k', '--keep-going', action='store_true',
+        help="Don't stop on first error.")
+    test_parser.add_argument('-i', '--isolate', action='store_true',
+        help="Run each test permutation in a separate process.")
+    test_parser.add_argument('-b', '--by-suites', action='store_true',
+        help="Step through tests by suite.")
+    test_parser.add_argument('-B', '--by-cases', action='store_true',
+        help="Step through tests by case.")
+    test_parser.add_argument('--gdb', action='store_true',
         help="Drop into gdb on test failure.")
-    parser.add_argument('--no-internal', action='store_true',
-        help="Don't run tests that require internal knowledge.")
-    parser.add_argument('-n', '--normal', action='store_true',
-        help="Run tests normally.")
-    parser.add_argument('-r', '--reentrant', action='store_true',
-        help="Run reentrant tests with simulated power-loss.")
-    parser.add_argument('--valgrind', action='store_true',
-        help="Run non-leaky tests under valgrind to check for memory leaks.")
-    parser.add_argument('--exec', default=[], type=lambda e: e.split(),
-        help="Run tests with another executable prefixed on the command line.")
-    parser.add_argument('--disk',
-        help="Specify a file to use for persistent/reentrant tests.")
-    parser.add_argument('--coverage', type=lambda x: x if x else True,
-        nargs='?', const='',
-        help="Collect coverage information during testing. This uses lcov/gcov \
-            to accumulate coverage information into *.info files. May also \
-            a path to a *.info file to accumulate coverage info into.")
-    parser.add_argument('--build-dir',
-        help="Build relative to the specified directory instead of the \
-            current directory.")
-
-    sys.exit(main(**vars(parser.parse_args())))
+    test_parser.add_argument('--gdb-case', action='store_true',
+        help="Drop into gdb on test failure but stop at the beginning \
+            of the failing test case.")
+    test_parser.add_argument('--gdb-main', action='store_true',
+        help="Drop into gdb on test failure but stop at the beginning \
+            of main.")
+    test_parser.add_argument('--valgrind', action='store_true',
+        help="Run under Valgrind to find memory errors. Implicitly sets \
+            --isolate.")
+    test_parser.add_argument('--exec', default=[], type=lambda e: e.split(),
+        help="Run under another executable.")
+    # compilation flags
+    comp_parser = parser.add_argument_group('compilation options')
+    comp_parser.add_argument('-c', '--compile', action='store_true',
+        help="Compile a test suite or source file.")
+    comp_parser.add_argument('-s', '--source',
+        help="Source file to compile, possibly injecting internal tests.")
+    comp_parser.add_argument('-o', '--output',
+        help="Output file.")
+    # TODO apply this to other scripts?
+    sys.exit(main(**{k: v
+        for k, v in vars(parser.parse_args()).items()
+        if v is not None}))

+ 0 - 1027
scripts/test_.py

@@ -1,1027 +0,0 @@
-#!/usr/bin/env python3
-#
-# Script to compile and runs tests.
-#
-
-import collections as co
-import errno
-import glob
-import itertools as it
-import math as m
-import os
-import pty
-import re
-import shlex
-import shutil
-import signal
-import subprocess as sp
-import threading as th
-import time
-import toml
-
-
-TEST_PATHS = ['tests_']
-RUNNER_PATH = './runners/test_runner'
-
-SUITE_PROLOGUE = """
-#include "runners/test_runner.h"
-#include "bd/lfs_testbd.h"
-#include <stdio.h>
-"""
-CASE_PROLOGUE = """
-"""
-CASE_EPILOGUE = """
-"""
-
-
-def testpath(path):
-    path, *_ = path.split('#', 1)
-    return path
-
-def testsuite(path):
-    suite = testpath(path)
-    suite = os.path.basename(suite)
-    if suite.endswith('.toml'):
-        suite = suite[:-len('.toml')]
-    return suite
-
-def testcase(path):
-    _, case, *_ = path.split('#', 2)
-    return '%s#%s' % (testsuite(path), case)
-
-# TODO move this out in other files
-def openio(path, mode='r'):
-    if path == '-':
-        if 'r' in mode:
-            return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
-        else:
-            return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
-    else:
-        return open(path, mode)
-
-class TestCase:
-    # create a TestCase object from a config
-    def __init__(self, config, args={}):
-        self.name = config.pop('name')
-        self.path = config.pop('path')
-        self.suite = config.pop('suite')
-        self.lineno = config.pop('lineno', None)
-        self.if_ = config.pop('if', None)
-        if isinstance(self.if_, bool):
-            self.if_ = 'true' if self.if_ else 'false'
-        self.code = config.pop('code')
-        self.code_lineno = config.pop('code_lineno', None)
-        self.in_ = config.pop('in',
-            config.pop('suite_in', None))
-
-        self.normal = config.pop('normal',
-            config.pop('suite_normal', True))
-        self.reentrant = config.pop('reentrant',
-            config.pop('suite_reentrant', False))
-
-        # figure out defines and build possible permutations
-        self.defines = set()
-        self.permutations = []
-
-        suite_defines = config.pop('suite_defines', {})
-        if not isinstance(suite_defines, list):
-            suite_defines = [suite_defines]
-        defines = config.pop('defines', {})
-        if not isinstance(defines, list):
-            defines = [defines]
-
-        # build possible permutations
-        for suite_defines_ in suite_defines:
-            self.defines |= suite_defines_.keys()
-            for defines_ in defines:
-                self.defines |= defines_.keys()
-                self.permutations.extend(map(dict, it.product(*(
-                    [(k, v) for v in (vs if isinstance(vs, list) else [vs])]
-                    for k, vs in sorted(
-                        (suite_defines_ | defines_).items())))))
-
-        for k in config.keys():
-            print('\x1b[01;33mwarning:\x1b[m in %s, found unused key %r'
-                % (self.id(), k),
-                file=sys.stderr)
-
-    def id(self):
-        return '%s#%s' % (self.suite, self.name)
-
-
-class TestSuite:
-    # create a TestSuite object from a toml file
-    def __init__(self, path, args={}):
-        self.name = testsuite(path)
-        self.path = testpath(path)
-
-        # load toml file and parse test cases
-        with open(self.path) as f:
-            # load tests
-            config = toml.load(f)
-
-            # find line numbers
-            f.seek(0)
-            case_linenos = []
-            code_linenos = []
-            for i, line in enumerate(f):
-                match = re.match(
-                    '(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
-                        '|' '(?P<code>code\s*=)',
-                    line)
-                if match and match.group('case'):
-                    case_linenos.append((i+1, match.group('name')))
-                elif match and match.group('code'):
-                    code_linenos.append(i+2)
-
-            # sort in case toml parsing did not retain order
-            case_linenos.sort()
-
-            cases = config.pop('cases')
-            for (lineno, name), (nlineno, _) in it.zip_longest(
-                    case_linenos, case_linenos[1:],
-                    fillvalue=(float('inf'), None)):
-                code_lineno = min(
-                    (l for l in code_linenos if l >= lineno and l < nlineno),
-                    default=None)
-                cases[name]['lineno'] = lineno
-                cases[name]['code_lineno'] = code_lineno
-
-            self.if_ = config.pop('if', None)
-            if isinstance(self.if_, bool):
-                self.if_ = 'true' if self.if_ else 'false'
-
-            self.code = config.pop('code', None)
-            self.code_lineno = min(
-                (l for l in code_linenos
-                    if not case_linenos or l < case_linenos[0][0]),
-                default=None)
-
-            # a couple of these we just forward to all cases
-            defines = config.pop('defines', {})
-            in_ = config.pop('in', None)
-            normal = config.pop('normal', True)
-            reentrant = config.pop('reentrant', False)
-
-            self.cases = []
-            for name, case in sorted(cases.items(),
-                    key=lambda c: c[1].get('lineno')):
-                self.cases.append(TestCase(config={
-                    'name': name,
-                    'path': path + (':%d' % case['lineno']
-                        if 'lineno' in case else ''),
-                    'suite': self.name,
-                    'suite_defines': defines,
-                    'suite_in': in_,
-                    'suite_normal': normal,
-                    'suite_reentrant': reentrant,
-                    **case}))
-
-            # combine per-case defines
-            self.defines = set.union(*(
-                set(case.defines) for case in self.cases))
-
-            # combine other per-case things
-            self.normal = any(case.normal for case in self.cases)
-            self.reentrant = any(case.reentrant for case in self.cases)
-
-        for k in config.keys():
-            print('\x1b[01;33mwarning:\x1b[m in %s, found unused key %r'
-                % (self.id(), k),
-                file=sys.stderr)
-
-    def id(self):
-        return self.name
-
-
-
-def compile(**args):
-    # find .toml files
-    paths = []
-    for path in args.get('test_ids', TEST_PATHS):
-        if os.path.isdir(path):
-            path = path + '/*.toml'
-
-        for path in glob.glob(path):
-            paths.append(path)
-
-    if not paths:
-        print('no test suites found in %r?' % args['test_ids'])
-        sys.exit(-1)
-
-    if not args.get('source'):
-        if len(paths) > 1:
-            print('more than one test suite for compilation? (%r)'
-                % args['test_ids'])
-            sys.exit(-1)
-
-        # load our suite
-        suite = TestSuite(paths[0])
-    else:
-        # load all suites
-        suites = [TestSuite(path) for path in paths]
-        suites.sort(key=lambda s: s.name)
-
-    # write generated test source
-    if 'output' in args:
-        with openio(args['output'], 'w') as f:
-            _write = f.write
-            def write(s):
-                f.lineno += s.count('\n')
-                _write(s)
-            def writeln(s=''):
-                f.lineno += s.count('\n') + 1
-                _write(s)
-                _write('\n')
-            f.lineno = 1
-            f.write = write
-            f.writeln = writeln
-
-            # redirect littlefs tracing
-            f.writeln('#define LFS_TRACE_(fmt, ...) do { \\')
-            f.writeln(8*' '+'extern FILE *test_trace; \\')
-            f.writeln(8*' '+'if (test_trace) { \\')
-            f.writeln(12*' '+'fprintf(test_trace, '
-                '"%s:%d:trace: " fmt "%s\\n", \\')
-            f.writeln(20*' '+'__FILE__, __LINE__, __VA_ARGS__); \\')
-            f.writeln(8*' '+'} \\')
-            f.writeln(4*' '+'} while (0)')
-            f.writeln('#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")')
-            f.writeln('#define LFS_TESTBD_TRACE(...) '
-                'LFS_TRACE_(__VA_ARGS__, "")')
-            f.writeln()
-
-            # write out generated functions, this can end up in different
-            # files depending on the "in" attribute
-            #
-            # note it's up to the specific generated file to declare
-            # the test defines
-            def write_case_functions(f, suite, case):
-                    # create case define functions
-                    if case.defines:
-                        # deduplicate defines by value to try to reduce the
-                        # number of functions we generate
-                        define_cbs = {}
-                        for i, defines in enumerate(case.permutations):
-                            for k, v in sorted(defines.items()):
-                                if v not in define_cbs:
-                                    name = ('__test__%s__%s__%s__%d'
-                                        % (suite.name, case.name, k, i))
-                                    define_cbs[v] = name
-                                    f.writeln('uintmax_t %s(void) {' % name)
-                                    f.writeln(4*' '+'return %s;' % v)
-                                    f.writeln('}')
-                                    f.writeln()
-                        f.writeln('uintmax_t (*const *const '
-                            '__test__%s__%s__defines[])(void) = {'
-                            % (suite.name, case.name))
-                        for defines in case.permutations:
-                            f.writeln(4*' '+'(uintmax_t (*const[])(void)){')
-                            for define in sorted(suite.defines):
-                                f.writeln(8*' '+'%s,' % (
-                                    define_cbs[defines[define]]
-                                        if define in defines
-                                        else 'NULL'))
-                            f.writeln(4*' '+'},')
-                        f.writeln('};')
-                        f.writeln()    
-
-                    # create case filter function
-                    if suite.if_ is not None or case.if_ is not None:
-                        f.writeln('bool __test__%s__%s__filter(void) {'
-                            % (suite.name, case.name))
-                        f.writeln(4*' '+'return %s;'
-                            % ' && '.join('(%s)' % if_
-                                for if_ in [suite.if_, case.if_]
-                                if if_ is not None))
-                        f.writeln('}')
-                        f.writeln()
-
-                    # create case run function
-                    f.writeln('void __test__%s__%s__run('
-                        '__attribute__((unused)) struct lfs_config *cfg) {'
-                        % (suite.name, case.name))
-                    if CASE_PROLOGUE.strip():
-                        f.writeln(4*' '+'%s'
-                            % CASE_PROLOGUE.strip().replace('\n', '\n'+4*' '))
-                        f.writeln()
-                    f.writeln(4*' '+'// test case %s' % case.id())
-                    if case.code_lineno is not None:
-                        f.writeln(4*' '+'#line %d "%s"'
-                            % (case.code_lineno, suite.path))
-                    f.write(case.code)
-                    if case.code_lineno is not None:
-                        f.writeln(4*' '+'#line %d "%s"'
-                            % (f.lineno+1, args['output']))
-                    if CASE_EPILOGUE.strip():
-                        f.writeln()
-                        f.writeln(4*' '+'%s'
-                            % CASE_EPILOGUE.strip().replace('\n', '\n'+4*' '))
-                    f.writeln('}')
-                    f.writeln()
-
-            if not args.get('source'):
-                # write test suite prologue
-                f.writeln('%s' % SUITE_PROLOGUE.strip())
-                f.writeln()
-                if suite.code is not None:
-                    if suite.code_lineno is not None:
-                        f.writeln('#line %d "%s"'
-                            % (suite.code_lineno, suite.path))
-                    f.write(suite.code)
-                    if suite.code_lineno is not None:
-                        f.writeln('#line %d "%s"'
-                            % (f.lineno+1, args['output']))
-                    f.writeln()
-
-                if suite.defines:
-                    for i, define in enumerate(sorted(suite.defines)):
-                        f.writeln('#ifndef %s' % define)
-                        f.writeln('#define %-24s test_define(%d)'
-                            % (define, i))
-                        f.writeln('#endif')
-                    f.writeln()
-
-                for case in suite.cases:
-                    # create case functions
-                    if case.in_ is None:
-                        write_case_functions(f, suite, case)
-                    else:
-                        if case.defines:
-                            f.writeln('extern uintmax_t (*const *const '
-                                '__test__%s__%s__defines[])(void);'
-                                % (suite.name, case.name))
-                        if suite.if_ is not None or case.if_ is not None:
-                            f.writeln('extern bool __test__%s__%s__filter('
-                                'void);'
-                                % (suite.name, case.name))
-                        f.writeln('extern void __test__%s__%s__run('
-                            'struct lfs_config *cfg);'
-                            % (suite.name, case.name))
-                        f.writeln()
-
-                    # create case struct
-                    f.writeln('const struct test_case __test__%s__%s__case = {'
-                        % (suite.name, case.name))
-                    f.writeln(4*' '+'.id = "%s",' % case.id())
-                    f.writeln(4*' '+'.name = "%s",' % case.name)
-                    f.writeln(4*' '+'.path = "%s",' % case.path)
-                    f.writeln(4*' '+'.types = %s,'
-                        % ' | '.join(filter(None, [
-                            'TEST_NORMAL' if case.normal else None,
-                            'TEST_REENTRANT' if case.reentrant else None])))
-                    f.writeln(4*' '+'.permutations = %d,'
-                        % len(case.permutations))
-                    if case.defines:
-                        f.writeln(4*' '+'.defines = __test__%s__%s__defines,'
-                            % (suite.name, case.name))
-                    if suite.if_ is not None or case.if_ is not None:
-                        f.writeln(4*' '+'.filter = __test__%s__%s__filter,'
-                            % (suite.name, case.name))
-                    f.writeln(4*' '+'.run = __test__%s__%s__run,'
-                        % (suite.name, case.name))
-                    f.writeln('};')
-                    f.writeln()
-
-                # create suite define names
-                if suite.defines:
-                    f.writeln('const char *const __test__%s__define_names[] = {'
-                        % suite.name)
-                    for k in sorted(suite.defines):
-                        f.writeln(4*' '+'"%s",' % k)
-                    f.writeln('};')
-                    f.writeln()
-
-                # create suite struct
-                f.writeln('const struct test_suite __test__%s__suite = {'
-                    % suite.name)
-                f.writeln(4*' '+'.id = "%s",' % suite.id())
-                f.writeln(4*' '+'.name = "%s",' % suite.name)
-                f.writeln(4*' '+'.path = "%s",' % suite.path)
-                f.writeln(4*' '+'.types = %s,'
-                    % ' | '.join(filter(None, [
-                        'TEST_NORMAL' if suite.normal else None,
-                        'TEST_REENTRANT' if suite.reentrant else None])))
-                if suite.defines:
-                    f.writeln(4*' '+'.define_names = __test__%s__define_names,'
-                        % suite.name)
-                f.writeln(4*' '+'.define_count = %d,' % len(suite.defines))
-                f.writeln(4*' '+'.cases = (const struct test_case *const []){')
-                for case in suite.cases:
-                    f.writeln(8*' '+'&__test__%s__%s__case,'
-                        % (suite.name, case.name))
-                f.writeln(4*' '+'},')
-                f.writeln(4*' '+'.case_count = %d,' % len(suite.cases))
-                f.writeln('};')
-                f.writeln()
-
-            else:
-                # copy source
-                f.writeln('#line 1 "%s"' % args['source'])
-                with open(args['source']) as sf:
-                    shutil.copyfileobj(sf, f)
-                f.writeln()
-
-                f.write(SUITE_PROLOGUE)
-                f.writeln()
-
-                # write any internal tests
-                for suite in suites:
-                    for case in suite.cases:
-                        if (case.in_ is not None
-                                and os.path.normpath(case.in_)
-                                    == os.path.normpath(args['source'])):
-                            # write defines, but note we need to undef any
-                            # new defines since we're in someone else's file
-                            if suite.defines:
-                                for i, define in enumerate(
-                                        sorted(suite.defines)):
-                                    f.writeln('#ifndef %s' % define)
-                                    f.writeln('#define %-24s test_define(%d)'
-                                        % (define, i))
-                                    f.writeln('#define __TEST__%s__NEEDS_UNDEF'
-                                        % define)
-                                    f.writeln('#endif')
-                                f.writeln()
-
-                            write_case_functions(f, suite, case)
-
-                            if suite.defines:
-                                for define in sorted(suite.defines):
-                                    f.writeln('#ifdef __TEST__%s__NEEDS_UNDEF'
-                                        % define)
-                                    f.writeln('#undef __TEST__%s__NEEDS_UNDEF'
-                                        % define)
-                                    f.writeln('#undef %s' % define)
-                                    f.writeln('#endif')
-                                f.writeln()
-
-                # add suite info to test_runner.c
-                if args['source'] == 'runners/test_runner.c':
-                    f.writeln()
-                    for suite in suites:
-                        f.writeln('extern const struct test_suite '
-                            '__test__%s__suite;' % suite.name)
-                    f.writeln('const struct test_suite *test_suites[] = {')
-                    for suite in suites:
-                        f.writeln(4*' '+'&__test__%s__suite,' % suite.name)
-                    f.writeln('};')
-                    f.writeln('const size_t test_suite_count = %d;'
-                        % len(suites))
-
-def runner(**args):
-    cmd = args['runner'].copy()
-    cmd.extend(args.get('test_ids'))
-
-    # run under some external command?
-    cmd[:0] = args.get('exec', [])
-
-    # run under valgrind?
-    if args.get('valgrind'):
-        cmd[:0] = filter(None, [
-            'valgrind',
-            '--leak-check=full',
-            '--track-origins=yes',
-            '--error-exitcode=4',
-            '-q'])
-
-    # filter tests?
-    if args.get('normal'):    cmd.append('-n')
-    if args.get('reentrant'): cmd.append('-r')
-    if args.get('geometry'):
-        cmd.append('-G%s' % args.get('geometry'))
-
-    # defines?
-    if args.get('define'):
-        for define in args.get('define'):
-            cmd.append('-D%s' % define)
-
-    return cmd
-
-def list_(**args):
-    cmd = runner(**args)
-    if args.get('summary'):         cmd.append('--summary')
-    if args.get('list_suites'):     cmd.append('--list-suites')
-    if args.get('list_cases'):      cmd.append('--list-cases')
-    if args.get('list_paths'):      cmd.append('--list-paths')
-    if args.get('list_defines'):    cmd.append('--list-defines')
-    if args.get('list_geometries'): cmd.append('--list-geometries')
-
-    if args.get('verbose'):
-        print(' '.join(shlex.quote(c) for c in cmd))
-    sys.exit(sp.call(cmd))
-
-
-def find_cases(runner_, **args):
-    # query from runner
-    cmd = runner_ + ['--list-cases']
-    if args.get('verbose'):
-        print(' '.join(shlex.quote(c) for c in cmd))
-    proc = sp.Popen(cmd,
-        stdout=sp.PIPE,
-        stderr=sp.PIPE if not args.get('verbose') else None,
-        universal_newlines=True,
-        errors='replace')
-    expected_suite_perms = co.defaultdict(lambda: 0)
-    expected_case_perms = co.defaultdict(lambda: 0)
-    expected_perms = 0
-    total_perms = 0
-    pattern = re.compile(
-        '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
-            '[^\s]+\s+(?P<filtered>\d+)/(?P<perms>\d+)')
-    # skip the first line
-    for line in it.islice(proc.stdout, 1, None):
-        m = pattern.match(line)
-        if m:
-            filtered = int(m.group('filtered'))
-            expected_suite_perms[m.group('suite')] += filtered
-            expected_case_perms[m.group('id')] += filtered
-            expected_perms += filtered
-            total_perms += int(m.group('perms'))
-    proc.wait()
-    if proc.returncode != 0:
-        if not args.get('verbose'):
-            for line in proc.stderr:
-                sys.stdout.write(line)
-        sys.exit(-1)
-
-    return (
-        expected_suite_perms,
-        expected_case_perms,
-        expected_perms,
-        total_perms)
-
-def find_paths(runner_, **args):
-    # query from runner
-    cmd = runner_ + ['--list-paths']
-    if args.get('verbose'):
-        print(' '.join(shlex.quote(c) for c in cmd))
-    proc = sp.Popen(cmd,
-        stdout=sp.PIPE,
-        stderr=sp.PIPE if not args.get('verbose') else None,
-        universal_newlines=True,
-        errors='replace')
-    paths = co.OrderedDict()
-    pattern = re.compile(
-        '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
-            '(?P<path>[^:]+):(?P<lineno>\d+)')
-    for line in proc.stdout:
-        m = pattern.match(line)
-        if m:
-            paths[m.group('id')] = (m.group('path'), int(m.group('lineno')))
-    proc.wait()
-    if proc.returncode != 0:
-        if not args.get('verbose'):
-            for line in proc.stderr:
-                sys.stdout.write(line)
-        sys.exit(-1)
-
-    return paths
-
-def find_defines(runner_, **args):
-    # query from runner
-    cmd = runner_ + ['--list-defines']
-    if args.get('verbose'):
-        print(' '.join(shlex.quote(c) for c in cmd))
-    proc = sp.Popen(cmd,
-        stdout=sp.PIPE,
-        stderr=sp.PIPE if not args.get('verbose') else None,
-        universal_newlines=True,
-        errors='replace')
-    defines = co.OrderedDict()
-    pattern = re.compile(
-        '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
-            '(?P<defines>(?:\w+=\w+\s*)+)')
-    for line in proc.stdout:
-        m = pattern.match(line)
-        if m:
-            defines[m.group('id')] = {k: v
-                for k, v in re.findall('(\w+)=(\w+)', m.group('defines'))}
-    proc.wait()
-    if proc.returncode != 0:
-        if not args.get('verbose'):
-            for line in proc.stderr:
-                sys.stdout.write(line)
-        sys.exit(-1)
-
-    return defines
-
-
-class TestFailure(Exception):
-    def __init__(self, id, returncode, output, assert_=None):
-        self.id = id
-        self.returncode = returncode
-        self.output = output
-        self.assert_ = assert_
-
-def run_stage(name, runner_, **args):
-    # get expected suite/case/perm counts
-    expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
-        find_cases(runner_, **args))
-
-    passed_suite_perms = co.defaultdict(lambda: 0)
-    passed_case_perms = co.defaultdict(lambda: 0)
-    passed_perms = 0
-    failures = []
-    killed = False
-
-    pattern = re.compile('^(?:'
-            '(?P<op>running|finished|skipped) '
-                '(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)'
-            '|' '(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
-                ' *(?P<message>.*)' ')$')
-    locals = th.local()
-    children = set()
-
-    def run_runner(runner_):
-        nonlocal passed_suite_perms
-        nonlocal passed_case_perms
-        nonlocal passed_perms
-        nonlocal locals
-
-        # run the tests!
-        cmd = runner_.copy()
-        if args.get('disk'):
-            cmd.append('--disk=%s' % args['disk'])
-        if args.get('trace'):
-            cmd.append('--trace=%s' % args['trace'])
-        if args.get('verbose'):
-            print(' '.join(shlex.quote(c) for c in cmd))
-        mpty, spty = pty.openpty()
-        proc = sp.Popen(cmd, stdout=spty, stderr=spty)
-        os.close(spty)
-        children.add(proc)
-        mpty = os.fdopen(mpty, 'r', 1)
-        if args.get('output'):
-            output = openio(args['output'], 'w')
-
-        last_id = None
-        last_output = []
-        last_assert = None
-        try:
-            while True:
-                # parse a line for state changes
-                try:
-                    line = mpty.readline()
-                except OSError as e:
-                    if e.errno == errno.EIO:
-                        break
-                    raise
-                if not line:
-                    break
-                last_output.append(line)
-                if args.get('output'):
-                    output.write(line)
-                elif args.get('verbose'):
-                    sys.stdout.write(line)
-
-                m = pattern.match(line)
-                if m:
-                    op = m.group('op') or m.group('op_')
-                    if op == 'running':
-                        locals.seen_perms += 1
-                        last_id = m.group('id')
-                        last_output = []
-                        last_assert = None
-                    elif op == 'finished':
-                        passed_suite_perms[m.group('suite')] += 1
-                        passed_case_perms[m.group('case')] += 1
-                        passed_perms += 1
-                    elif op == 'skipped':
-                        locals.seen_perms += 1
-                    elif op == 'assert':
-                        last_assert = (
-                            m.group('path'),
-                            int(m.group('lineno')),
-                            m.group('message'))
-                        # go ahead and kill the process, aborting takes a while
-                        if args.get('keep_going'):
-                            proc.kill()
-        except KeyboardInterrupt:
-            raise TestFailure(last_id, 1, last_output)
-        finally:
-            children.remove(proc)
-            mpty.close()
-            if args.get('output'):
-                output.close()
-
-        proc.wait()
-        if proc.returncode != 0:
-            raise TestFailure(
-                last_id,
-                proc.returncode,
-                last_output,
-                last_assert)
-
-    def run_job(runner, start=None, step=None):
-        nonlocal failures
-        nonlocal locals
-
-        start = start or 0
-        step = step or 1
-        while start < total_perms:
-            runner_ = runner.copy()
-            if start is not None:
-                runner_.append('--start=%d' % start)
-            if step is not None:
-                runner_.append('--step=%d' % step)
-            if args.get('isolate') or args.get('valgrind'):
-                runner_.append('--stop=%d' % (start+step))
-
-            try:
-                # run the tests
-                locals.seen_perms = 0
-                run_runner(runner_)
-                assert locals.seen_perms > 0
-                start += locals.seen_perms*step
-
-            except TestFailure as failure:
-                # race condition for multiple failures?
-                if failures and not args.get('keep_going'):
-                    break
-
-                failures.append(failure)
-
-                if args.get('keep_going') and not killed:
-                    # resume after failed test
-                    assert locals.seen_perms > 0
-                    start += locals.seen_perms*step
-                    continue
-                else:
-                    # stop other tests
-                    for child in children.copy():
-                        child.kill()
-    
-
-    # parallel jobs?
-    runners = []
-    if 'jobs' in args:
-        for job in range(args['jobs']):
-            runners.append(th.Thread(
-                target=run_job, args=(runner_, job, args['jobs'])))
-    else:
-        runners.append(th.Thread(
-            target=run_job, args=(runner_, None, None)))
-
-    for r in runners:
-        r.start()
-
-    needs_newline = False
-    try:
-        while any(r.is_alive() for r in runners):
-            time.sleep(0.01)
-
-            if not args.get('verbose'):
-                sys.stdout.write('\r\x1b[K'
-                    'running \x1b[%dm%s:\x1b[m %s '
-                    % (32 if not failures else 31,
-                        name,
-                        ', '.join(filter(None, [
-                            '%d/%d suites' % (
-                                sum(passed_suite_perms[k] == v
-                                    for k, v in expected_suite_perms.items()),
-                                len(expected_suite_perms))
-                                if (not args.get('by_suites')
-                                    and not args.get('by_cases')) else None,
-                            '%d/%d cases' % (
-                                sum(passed_case_perms[k] == v
-                                    for k, v in expected_case_perms.items()),
-                                len(expected_case_perms))
-                                if not args.get('by_cases') else None,
-                            '%d/%d perms' % (passed_perms, expected_perms),
-                            '\x1b[31m%d/%d failures\x1b[m'
-                                % (len(failures), expected_perms)
-                                if failures else None]))))
-                sys.stdout.flush()
-                needs_newline = True
-    except KeyboardInterrupt:
-        # this is handled by the runner threads, we just
-        # need to not abort here
-        killed = True
-    finally:
-        if needs_newline:
-            print()
-
-    for r in runners:
-        r.join()
-
-    return (
-        expected_perms,
-        passed_perms,
-        failures,
-        killed)
-    
-
-def run(**args):
-    start = time.time()
-
-    runner_ = runner(**args)
-    print('using runner: %s'
-        % ' '.join(shlex.quote(c) for c in runner_))
-    expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
-        find_cases(runner_, **args))
-    print('found %d suites, %d cases, %d/%d permutations'
-        % (len(expected_suite_perms),
-            len(expected_case_perms),
-            expected_perms,
-            total_perms))
-    print()
-
-    expected = 0
-    passed = 0
-    failures = []
-    for type, by in it.product(
-            ['normal', 'reentrant'],
-            expected_case_perms.keys() if args.get('by_cases')
-                else expected_suite_perms.keys() if args.get('by_suites')
-                else [None]):
-        # rebuild runner for each stage to override test identifier if needed
-        stage_runner = runner(**args | {
-            'test_ids': [by] if by is not None else args.get('test_ids', []),
-            'normal': type == 'normal',
-            'reentrant': type == 'reentrant'})
-
-        # spawn jobs for stage
-        expected_, passed_, failures_, killed = run_stage(
-            '%s %s' % (type, by or 'tests'), stage_runner, **args)
-        expected += expected_
-        passed += passed_
-        failures.extend(failures_)
-        if (failures and not args.get('keep_going')) or killed:
-            break
-
-    # show summary
-    print()
-    print('\x1b[%dmdone:\x1b[m %d/%d passed, %d/%d failed, in %.2fs'
-        % (32 if not failures else 31,
-            passed, expected, len(failures), expected,
-            time.time()-start))
-    print()
-
-    # print each failure
-    if failures:
-        # get some extra info from runner
-        runner_paths = find_paths(runner_, **args)
-        runner_defines = find_defines(runner_, **args)
-
-    for failure in failures:
-        # show summary of failure
-        path, lineno = runner_paths[testcase(failure.id)]
-        defines = runner_defines[failure.id]
-
-        print('\x1b[01m%s:%d:\x1b[01;31mfailure:\x1b[m %s%s failed'
-            % (path, lineno, failure.id,
-                ' (%s)' % ', '.join(
-                    '%s=%s' % (k, v) for k, v in defines.items())
-                if defines else ''))
-
-        if failure.output:
-            output = failure.output
-            if failure.assert_ is not None:
-                output = output[:-1]
-            for line in output[-5:]:
-                sys.stdout.write(line)
-
-        if failure.assert_ is not None:
-            path, lineno, message = failure.assert_
-            print('\x1b[01m%s:%d:\x1b[01;31massert:\x1b[m %s'
-                % (path, lineno, message))
-            with open(path) as f:
-                line = next(it.islice(f, lineno-1, None)).strip('\n')
-                print(line)
-        print()
-
-    # drop into gdb?
-    if failures and (args.get('gdb')
-            or args.get('gdb_case')
-            or args.get('gdb_main')):
-        failure = failures[0]
-        runner_ = runner(**args | {'test_ids': [failure.id]})
-
-        if args.get('gdb_main'):
-            cmd = ['gdb',
-                '-ex', 'break main',
-                '-ex', 'run',
-                '--args'] + runner_
-        elif args.get('gdb_case'):
-            path, lineno = runner_paths[testcase(failure.id)]
-            cmd = ['gdb',
-                '-ex', 'break %s:%d' % (path, lineno),
-                '-ex', 'run',
-                '--args'] + runner_
-        elif failure.assert_ is not None:
-            cmd = ['gdb',
-                '-ex', 'run',
-                '-ex', 'frame function raise',
-                '-ex', 'up 2',
-                '--args'] + runner_
-        else:
-            cmd = ['gdb',
-                '-ex', 'run',
-                '--args'] + runner_
-
-        # exec gdb interactively
-        if args.get('verbose'):
-            print(' '.join(shlex.quote(c) for c in cmd))
-        os.execvp(cmd[0], cmd)
-
-    return 1 if failures else 0
-
-
-def main(**args):
-    if args.get('compile'):
-        compile(**args)
-    elif (args.get('summary')
-            or args.get('list_suites')
-            or args.get('list_cases')
-            or args.get('list_paths')
-            or args.get('list_defines')
-            or args.get('list_geometries')
-            or args.get('list_defaults')):
-        list_(**args)
-    else:
-        run(**args)
-
-
-if __name__ == "__main__":
-    import argparse
-    import sys
-    parser = argparse.ArgumentParser(
-        description="Build and run tests.",
-        conflict_handler='resolve')
-    parser.add_argument('test_ids', nargs='*',
-        help="Description of testis to run. May be a directory, path, or \
-            test identifier. Test identifiers are of the form \
-            <suite_name>#<case_name>#<permutation>, but suffixes can be \
-            dropped to run any matching tests. Defaults to %r." % TEST_PATHS)
-    parser.add_argument('-v', '--verbose', action='store_true',
-        help="Output commands that run behind the scenes.")
-    # test flags
-    test_parser = parser.add_argument_group('test options')
-    test_parser.add_argument('-Y', '--summary', action='store_true',
-        help="Show quick summary.")
-    test_parser.add_argument('-l', '--list-suites', action='store_true',
-        help="List test suites.")
-    test_parser.add_argument('-L', '--list-cases', action='store_true',
-        help="List test cases.")
-    test_parser.add_argument('--list-paths', action='store_true',
-        help="List the path for each test case.")
-    test_parser.add_argument('--list-defines', action='store_true',
-        help="List the defines for each test permutation.")
-    test_parser.add_argument('--list-geometries', action='store_true',
-        help="List the disk geometries used for testing.")
-    test_parser.add_argument('--list-defaults', action='store_true',
-        help="List the default defines in this test-runner.")
-    test_parser.add_argument('-D', '--define', action='append',
-        help="Override a test define.")
-    test_parser.add_argument('-G', '--geometry',
-        help="Filter by geometry.")
-    test_parser.add_argument('-n', '--normal', action='store_true',
-        help="Filter for normal tests. Can be combined.")
-    test_parser.add_argument('-r', '--reentrant', action='store_true',
-        help="Filter for reentrant tests. Can be combined.")
-    test_parser.add_argument('-d', '--disk',
-        help="Use this file as the disk.")
-    test_parser.add_argument('-t', '--trace',
-        help="Redirect trace output to this file.")
-    test_parser.add_argument('-o', '--output',
-        help="Redirect stdout and stderr to this file.")
-    test_parser.add_argument('--runner', default=[RUNNER_PATH],
-        type=lambda x: x.split(),
-        help="Path to runner, defaults to %r" % RUNNER_PATH)
-    test_parser.add_argument('-j', '--jobs', nargs='?', type=int,
-        const=len(os.sched_getaffinity(0)),
-        help="Number of parallel runners to run.")
-    test_parser.add_argument('-k', '--keep-going', action='store_true',
-        help="Don't stop on first error.")
-    test_parser.add_argument('-i', '--isolate', action='store_true',
-        help="Run each test permutation in a separate process.")
-    test_parser.add_argument('-b', '--by-suites', action='store_true',
-        help="Step through tests by suite.")
-    test_parser.add_argument('-B', '--by-cases', action='store_true',
-        help="Step through tests by case.")
-    test_parser.add_argument('--gdb', action='store_true',
-        help="Drop into gdb on test failure.")
-    test_parser.add_argument('--gdb-case', action='store_true',
-        help="Drop into gdb on test failure but stop at the beginning \
-            of the failing test case.")
-    test_parser.add_argument('--gdb-main', action='store_true',
-        help="Drop into gdb on test failure but stop at the beginning \
-            of main.")
-    test_parser.add_argument('--valgrind', action='store_true',
-        help="Run under Valgrind to find memory errors. Implicitly sets \
-            --isolate.")
-    test_parser.add_argument('--exec', default=[], type=lambda e: e.split(),
-        help="Run under another executable.")
-    # compilation flags
-    comp_parser = parser.add_argument_group('compilation options')
-    comp_parser.add_argument('-c', '--compile', action='store_true',
-        help="Compile a test suite or source file.")
-    comp_parser.add_argument('-s', '--source',
-        help="Source file to compile, possibly injecting internal tests.")
-    comp_parser.add_argument('-o', '--output',
-        help="Output file.")
-    # TODO apply this to other scripts?
-    sys.exit(main(**{k: v
-        for k, v in vars(parser.parse_args()).items()
-        if v is not None}))

+ 178 - 114
tests/test_alloc.toml

@@ -1,27 +1,30 @@
 # allocator tests
 # note for these to work there are a number constraints on the device geometry
-if = 'LFS_BLOCK_CYCLES == -1'
+if = 'BLOCK_CYCLES == -1'
 
-[[case]] # parallel allocation test
-define.FILES = 3
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-6)) / FILES)'
+# parallel allocation test
+[cases.parallel_allocation]
+defines.FILES = 3
+defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
 code = '''
-    const char *names[FILES] = {"bacon", "eggs", "pancakes"};
+    const char *names[] = {"bacon", "eggs", "pancakes"};
     lfs_file_t files[FILES];
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "breakfast") => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int n = 0; n < FILES; n++) {
+        char path[1024];
         sprintf(path, "breakfast/%s", names[n]);
         lfs_file_open(&lfs, &files[n], path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
     }
     for (int n = 0; n < FILES; n++) {
-        size = strlen(names[n]);
+        size_t size = strlen(names[n]);
         for (lfs_size_t i = 0; i < SIZE; i += size) {
             lfs_file_write(&lfs, &files[n], names[n], size) => size;
         }
@@ -31,12 +34,15 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int n = 0; n < FILES; n++) {
+        char path[1024];
         sprintf(path, "breakfast/%s", names[n]);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
-        size = strlen(names[n]);
+        size_t size = strlen(names[n]);
         for (lfs_size_t i = 0; i < SIZE; i += size) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &file, buffer, size) => size;
             assert(memcmp(buffer, names[n], size) == 0);
         }
@@ -45,23 +51,28 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # serial allocation test
-define.FILES = 3
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-6)) / FILES)'
+# serial allocation test
+[cases.serial_allocation]
+defines.FILES = 3
+defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
 code = '''
-    const char *names[FILES] = {"bacon", "eggs", "pancakes"};
+    const char *names[] = {"bacon", "eggs", "pancakes"};
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "breakfast") => 0;
     lfs_unmount(&lfs) => 0;
 
     for (int n = 0; n < FILES; n++) {
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
+        char path[1024];
         sprintf(path, "breakfast/%s", names[n]);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
-        size = strlen(names[n]);
+        size_t size = strlen(names[n]);
+        uint8_t buffer[1024];
         memcpy(buffer, names[n], size);
         for (int i = 0; i < SIZE; i += size) {
             lfs_file_write(&lfs, &file, buffer, size) => size;
@@ -70,12 +81,15 @@ code = '''
         lfs_unmount(&lfs) => 0;
     }
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int n = 0; n < FILES; n++) {
+        char path[1024];
         sprintf(path, "breakfast/%s", names[n]);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
-        size = strlen(names[n]);
+        size_t size = strlen(names[n]);
         for (int i = 0; i < SIZE; i += size) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &file, buffer, size) => size;
             assert(memcmp(buffer, names[n], size) == 0);
         }
@@ -84,29 +98,32 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # parallel allocation reuse test
-define.FILES = 3
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-6)) / FILES)'
-define.CYCLES = [1, 10]
+# parallel allocation reuse test
+[cases.parallel_allocation_reuse]
+defines.FILES = 3
+defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
+defines.CYCLES = [1, 10]
 code = '''
-    const char *names[FILES] = {"bacon", "eggs", "pancakes"};
+    const char *names[] = {"bacon", "eggs", "pancakes"};
     lfs_file_t files[FILES];
 
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     for (int c = 0; c < CYCLES; c++) {
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         lfs_mkdir(&lfs, "breakfast") => 0;
         lfs_unmount(&lfs) => 0;
 
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (int n = 0; n < FILES; n++) {
+            char path[1024];
             sprintf(path, "breakfast/%s", names[n]);
             lfs_file_open(&lfs, &files[n], path,
                     LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
         }
         for (int n = 0; n < FILES; n++) {
-            size = strlen(names[n]);
+            size_t size = strlen(names[n]);
             for (int i = 0; i < SIZE; i += size) {
                 lfs_file_write(&lfs, &files[n], names[n], size) => size;
             }
@@ -116,12 +133,15 @@ code = '''
         }
         lfs_unmount(&lfs) => 0;
 
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (int n = 0; n < FILES; n++) {
+            char path[1024];
             sprintf(path, "breakfast/%s", names[n]);
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
-            size = strlen(names[n]);
+            size_t size = strlen(names[n]);
             for (int i = 0; i < SIZE; i += size) {
+                uint8_t buffer[1024];
                 lfs_file_read(&lfs, &file, buffer, size) => size;
                 assert(memcmp(buffer, names[n], size) == 0);
             }
@@ -129,8 +149,9 @@ code = '''
         }
         lfs_unmount(&lfs) => 0;
 
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (int n = 0; n < FILES; n++) {
+            char path[1024];
             sprintf(path, "breakfast/%s", names[n]);
             lfs_remove(&lfs, path) => 0;
         }
@@ -139,26 +160,31 @@ code = '''
     }
 '''
 
-[[case]] # serial allocation reuse test
-define.FILES = 3
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-6)) / FILES)'
-define.CYCLES = [1, 10]
+# serial allocation reuse test
+[cases.serial_allocation_reuse]
+defines.FILES = 3
+defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
+defines.CYCLES = [1, 10]
 code = '''
-    const char *names[FILES] = {"bacon", "eggs", "pancakes"};
+    const char *names[] = {"bacon", "eggs", "pancakes"};
 
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     for (int c = 0; c < CYCLES; c++) {
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         lfs_mkdir(&lfs, "breakfast") => 0;
         lfs_unmount(&lfs) => 0;
 
         for (int n = 0; n < FILES; n++) {
-            lfs_mount(&lfs, &cfg) => 0;
+            lfs_mount(&lfs, cfg) => 0;
+            char path[1024];
             sprintf(path, "breakfast/%s", names[n]);
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path,
                     LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
-            size = strlen(names[n]);
+            size_t size = strlen(names[n]);
+            uint8_t buffer[1024];
             memcpy(buffer, names[n], size);
             for (int i = 0; i < SIZE; i += size) {
                 lfs_file_write(&lfs, &file, buffer, size) => size;
@@ -167,12 +193,15 @@ code = '''
             lfs_unmount(&lfs) => 0;
         }
 
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (int n = 0; n < FILES; n++) {
+            char path[1024];
             sprintf(path, "breakfast/%s", names[n]);
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
-            size = strlen(names[n]);
+            size_t size = strlen(names[n]);
             for (int i = 0; i < SIZE; i += size) {
+                uint8_t buffer[1024];
                 lfs_file_read(&lfs, &file, buffer, size) => size;
                 assert(memcmp(buffer, names[n], size) == 0);
             }
@@ -180,8 +209,9 @@ code = '''
         }
         lfs_unmount(&lfs) => 0;
 
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (int n = 0; n < FILES; n++) {
+            char path[1024];
             sprintf(path, "breakfast/%s", names[n]);
             lfs_remove(&lfs, path) => 0;
         }
@@ -190,12 +220,16 @@ code = '''
     }
 '''
 
-[[case]] # exhaustion test
+# exhaustion test
+[cases.exhaustion]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
-    size = strlen("exhaustion");
+    size_t size = strlen("exhaustion");
+    uint8_t buffer[1024];
     memcpy(buffer, "exhaustion", size);
     lfs_file_write(&lfs, &file, buffer, size) => size;
     lfs_file_sync(&lfs, &file) => 0;
@@ -216,7 +250,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY);
     size = strlen("exhaustion");
     lfs_file_size(&lfs, &file) => size;
@@ -226,14 +260,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # exhaustion wraparound test
-define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / 3)'
+# exhaustion wraparound test
+[cases.exhaustion_wraparound]
+defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-4)) / 3)'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "padding", LFS_O_WRONLY | LFS_O_CREAT);
-    size = strlen("buffering");
+    size_t size = strlen("buffering");
+    uint8_t buffer[1024];
     memcpy(buffer, "buffering", size);
     for (int i = 0; i < SIZE; i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
@@ -263,7 +301,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY);
     size = strlen("exhaustion");
     lfs_file_size(&lfs, &file) => size;
@@ -274,17 +312,22 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # dir exhaustion test
+# dir exhaustion test
+[cases.dir_exhaustion]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // find out max file size
     lfs_mkdir(&lfs, "exhaustiondir") => 0;
-    size = strlen("blahblahblahblah");
+    size_t size = strlen("blahblahblahblah");
+    uint8_t buffer[1024];
     memcpy(buffer, "blahblahblahblah", size);
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
     int count = 0;
+    int err;
     while (true) {
         err = lfs_file_write(&lfs, &file, buffer, size);
         if (err < 0) {
@@ -323,17 +366,21 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # what if we have a bad block during an allocation scan?
+# what if we have a bad block during an allocation scan?
+[cases.bad_block_allocation]
 in = "lfs.c"
-define.LFS_ERASE_CYCLES = 0xffffffff
-define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_READERROR'
+defines.ERASE_CYCLES = 0xffffffff
+defines.BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_READERROR'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     // first fill to exhaustion to find available space
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    uint8_t buffer[1024];
     strcpy((char*)buffer, "waka");
-    size = strlen("waka");
+    size_t size = strlen("waka");
     lfs_size_t filesize = 0;
     while (true) {
         lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
@@ -345,7 +392,7 @@ code = '''
     }
     lfs_file_close(&lfs, &file) => 0;
     // now fill all but a couple of blocks of the filesystem with data
-    filesize -= 3*LFS_BLOCK_SIZE;
+    filesize -= 3*BLOCK_SIZE;
     lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     strcpy((char*)buffer, "waka");
     size = strlen("waka");
@@ -358,11 +405,11 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // remount to force an alloc scan
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // but mark the head of our file as a "bad block", this is force our
     // scan to bail early
-    lfs_testbd_setwear(&cfg, fileblock, 0xffffffff) => 0;
+    lfs_testbd_setwear(cfg, fileblock, 0xffffffff) => 0;
     lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     strcpy((char*)buffer, "chomp");
     size = strlen("chomp");
@@ -377,7 +424,7 @@ code = '''
 
     // now reverse the "bad block" and try to write the file again until we
     // run out of space
-    lfs_testbd_setwear(&cfg, fileblock, 0) => 0;
+    lfs_testbd_setwear(cfg, fileblock, 0) => 0;
     lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     strcpy((char*)buffer, "chomp");
     size = strlen("chomp");
@@ -393,7 +440,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // check that the disk isn't hurt
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0;
     strcpy((char*)buffer, "waka");
     size = strlen("waka");
@@ -411,24 +458,29 @@ code = '''
 # on the geometry of the block device. But they are valuable. Eventually they
 # 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 && LFS_BLOCK_COUNT == 1024'
+# chained dir exhaustion test
+[cases.chained_dir_exhaustion]
+if = 'BLOCK_SIZE == 512'
+defines.BLOCK_COUNT = 1024
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // find out max file size
     lfs_mkdir(&lfs, "exhaustiondir") => 0;
     for (int i = 0; i < 10; i++) {
+        char path[1024];
         sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
         lfs_mkdir(&lfs, path) => 0;
     }
-    size = strlen("blahblahblahblah");
+    size_t size = strlen("blahblahblahblah");
+    uint8_t buffer[1024];
     memcpy(buffer, "blahblahblahblah", size);
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
     int count = 0;
+    int err;
     while (true) {
         err = lfs_file_write(&lfs, &file, buffer, size);
         if (err < 0) {
@@ -443,6 +495,7 @@ code = '''
     lfs_remove(&lfs, "exhaustion") => 0;
     lfs_remove(&lfs, "exhaustiondir") => 0;
     for (int i = 0; i < 10; i++) {
+        char path[1024];
         sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
         lfs_remove(&lfs, path) => 0;
     }
@@ -455,6 +508,7 @@ code = '''
     lfs_file_sync(&lfs, &file) => 0;
 
     for (int i = 0; i < 10; i++) {
+        char path[1024];
         sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
         lfs_mkdir(&lfs, path) => 0;
     }
@@ -482,27 +536,31 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # split dir test
-define.LFS_BLOCK_SIZE = 512
-define.LFS_BLOCK_COUNT = 1024
-if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024'
+# split dir test
+[cases.split_dir]
+if = 'BLOCK_SIZE == 512'
+defines.BLOCK_COUNT = 1024
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // create one block hole for half a directory
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
-    for (lfs_size_t i = 0; i < cfg.block_size; i += 2) {
+    for (lfs_size_t i = 0; i < cfg->block_size; i += 2) {
+        uint8_t buffer[1024];
         memcpy(&buffer[i], "hi", 2);
     }
-    lfs_file_write(&lfs, &file, buffer, cfg.block_size) => cfg.block_size;
+    uint8_t buffer[1024];
+    lfs_file_write(&lfs, &file, buffer, cfg->block_size) => cfg->block_size;
     lfs_file_close(&lfs, &file) => 0;
 
     lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
-    size = strlen("blahblahblahblah");
+    size_t size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
-            i < (cfg.block_count-4)*(cfg.block_size-8);
+            i < (cfg->block_count-4)*(cfg->block_size-8);
             i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -510,7 +568,7 @@ code = '''
 
     // remount to force reset of lookahead
     lfs_unmount(&lfs) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // open hole
     lfs_remove(&lfs, "bump") => 0;
@@ -518,30 +576,33 @@ code = '''
     lfs_mkdir(&lfs, "splitdir") => 0;
     lfs_file_open(&lfs, &file, "splitdir/bump",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
-    for (lfs_size_t i = 0; i < cfg.block_size; i += 2) {
+    for (lfs_size_t i = 0; i < cfg->block_size; i += 2) {
         memcpy(&buffer[i], "hi", 2);
     }
-    lfs_file_write(&lfs, &file, buffer, 2*cfg.block_size) => LFS_ERR_NOSPC;
+    lfs_file_write(&lfs, &file, buffer, 2*cfg->block_size) => LFS_ERR_NOSPC;
     lfs_file_close(&lfs, &file) => 0;
 
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # outdated lookahead test
-define.LFS_BLOCK_SIZE = 512
-define.LFS_BLOCK_COUNT = 1024
-if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024'
+# outdated lookahead test
+[cases.outdated_lookahead]
+if = 'BLOCK_SIZE == 512'
+defines.BLOCK_COUNT = 1024
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // fill completely with two files
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "exhaustion1",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
-    size = strlen("blahblahblahblah");
+    size_t size = strlen("blahblahblahblah");
+    uint8_t buffer[1024];
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
+            i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
             i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -552,7 +613,7 @@ code = '''
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
+            i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
             i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -560,7 +621,7 @@ code = '''
 
     // remount to force reset of lookahead
     lfs_unmount(&lfs) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // rewrite one file
     lfs_file_open(&lfs, &file, "exhaustion1",
@@ -569,7 +630,7 @@ code = '''
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
+            i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
             i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -583,7 +644,7 @@ code = '''
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
+            i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
             i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -592,21 +653,24 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # outdated lookahead and split dir test
-define.LFS_BLOCK_SIZE = 512
-define.LFS_BLOCK_COUNT = 1024
-if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024'
+# outdated lookahead and split dir test
+[cases.outdated_lookahead_split_dir]
+if = 'BLOCK_SIZE == 512'
+defines.BLOCK_COUNT = 1024
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // fill completely with two files
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "exhaustion1",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
-    size = strlen("blahblahblahblah");
+    size_t size = strlen("blahblahblahblah");
+    uint8_t buffer[1024];
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
+            i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
             i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -617,7 +681,7 @@ code = '''
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
+            i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
             i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -625,7 +689,7 @@ code = '''
 
     // remount to force reset of lookahead
     lfs_unmount(&lfs) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // rewrite one file with a hole of one block
     lfs_file_open(&lfs, &file, "exhaustion1",
@@ -634,7 +698,7 @@ code = '''
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-2)/2 - 1)*(cfg.block_size-8);
+            i < ((cfg->block_count-2)/2 - 1)*(cfg->block_size-8);
             i += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }

+ 31 - 19
tests/test_attrs.toml

@@ -1,14 +1,17 @@
-[[case]] # set/get attribute
+[cases.get_set_attrs]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "hello") => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     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;
+    uint8_t buffer[1024];
     memset(buffer, 0, sizeof(buffer));
     lfs_setattr(&lfs, "hello", 'A', "aaaa",   4) => 0;
     lfs_setattr(&lfs, "hello", 'B', "bbbbbb", 6) => 0;
@@ -60,7 +63,7 @@ code = '''
 
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     memset(buffer, 0, sizeof(buffer));
     lfs_getattr(&lfs, "hello", 'A', buffer,    4) => 4;
     lfs_getattr(&lfs, "hello", 'B', buffer+4,  9) => 9;
@@ -76,17 +79,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # set/get root attribute
+[cases.get_set_root_attrs]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "hello") => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     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;
+    uint8_t buffer[1024];
     memset(buffer, 0, sizeof(buffer));
     lfs_setattr(&lfs, "/", 'A', "aaaa",   4) => 0;
     lfs_setattr(&lfs, "/", 'B', "bbbbbb", 6) => 0;
@@ -137,7 +143,7 @@ code = '''
     lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     memset(buffer, 0, sizeof(buffer));
     lfs_getattr(&lfs, "/", 'A', buffer,    4) => 4;
     lfs_getattr(&lfs, "/", 'B', buffer+4,  9) => 9;
@@ -153,17 +159,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # set/get file attribute
+[cases.get_set_file_attrs]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "hello") => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     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;
+    uint8_t buffer[1024];
     memset(buffer, 0, sizeof(buffer));
     struct lfs_attr attrs1[] = {
         {'A', buffer,    4},
@@ -238,7 +247,7 @@ code = '''
 
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     memset(buffer, 0, sizeof(buffer));
     struct lfs_attr attrs3[] = {
         {'A', buffer,    4},
@@ -260,20 +269,23 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # deferred file attributes
+[cases.deferred_file_attrs]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "hello") => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     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;
     lfs_setattr(&lfs, "hello/hello", 'B', "fffffffff",  9) => 0;
     lfs_setattr(&lfs, "hello/hello", 'C', "ccccc",      5) => 0;
 
+    uint8_t buffer[1024];
     memset(buffer, 0, sizeof(buffer));
     struct lfs_attr attrs1[] = {
         {'B', "gggg", 4},

+ 74 - 55
tests/test_badblocks.toml

@@ -1,28 +1,30 @@
 # bad blocks with block cycles should be tested in test_relocations
-if = 'LFS_BLOCK_CYCLES == -1'
+if = '(int32_t)BLOCK_CYCLES == -1'
 
-[[case]] # single bad blocks
-define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
-define.LFS_ERASE_CYCLES = 0xffffffff
-define.LFS_ERASE_VALUE = [0x00, 0xff, -1]
-define.LFS_BADBLOCK_BEHAVIOR = [
+[cases.single_bad_blocks]
+defines.BLOCK_COUNT = 256 # small bd so test runs faster
+defines.ERASE_CYCLES = 0xffffffff
+defines.ERASE_VALUE = [0x00, 0xff, -1]
+defines.BADBLOCK_BEHAVIOR = [
     'LFS_TESTBD_BADBLOCK_PROGERROR',
     'LFS_TESTBD_BADBLOCK_ERASEERROR',
     'LFS_TESTBD_BADBLOCK_READERROR',
     'LFS_TESTBD_BADBLOCK_PROGNOOP',
     'LFS_TESTBD_BADBLOCK_ERASENOOP',
 ]
-define.NAMEMULT = 64
-define.FILEMULT = 1
+defines.NAMEMULT = 64
+defines.FILEMULT = 1
 code = '''
-    for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) {
-        lfs_testbd_setwear(&cfg, badblock-1, 0) => 0;
-        lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0;
-        
-        lfs_format(&lfs, &cfg) => 0;
+    for (lfs_block_t badblock = 2; badblock < BLOCK_COUNT; badblock++) {
+        lfs_testbd_setwear(cfg, badblock-1, 0) => 0;
+        lfs_testbd_setwear(cfg, badblock, 0xffffffff) => 0;
+
+        lfs_t lfs;
+        lfs_format(&lfs, cfg) => 0;
 
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (int i = 1; i < 10; i++) {
+            uint8_t buffer[1024];
             for (int j = 0; j < NAMEMULT; j++) {
                 buffer[j] = '0'+i;
             }
@@ -34,10 +36,11 @@ code = '''
                 buffer[j+NAMEMULT+1] = '0'+i;
             }
             buffer[2*NAMEMULT+1] = '\0';
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, (char*)buffer,
                     LFS_O_WRONLY | LFS_O_CREAT) => 0;
             
-            size = NAMEMULT;
+            lfs_size_t size = NAMEMULT;
             for (int j = 0; j < i*FILEMULT; j++) {
                 lfs_file_write(&lfs, &file, buffer, size) => size;
             }
@@ -46,12 +49,14 @@ code = '''
         }
         lfs_unmount(&lfs) => 0;
 
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (int i = 1; i < 10; i++) {
+            uint8_t buffer[1024];
             for (int j = 0; j < NAMEMULT; j++) {
                 buffer[j] = '0'+i;
             }
             buffer[NAMEMULT] = '\0';
+            struct lfs_info info;
             lfs_stat(&lfs, (char*)buffer, &info) => 0;
             info.type => LFS_TYPE_DIR;
 
@@ -60,9 +65,10 @@ code = '''
                 buffer[j+NAMEMULT+1] = '0'+i;
             }
             buffer[2*NAMEMULT+1] = '\0';
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0;
             
-            size = NAMEMULT;
+            int size = NAMEMULT;
             for (int j = 0; j < i*FILEMULT; j++) {
                 uint8_t rbuffer[1024];
                 lfs_file_read(&lfs, &file, rbuffer, size) => size;
@@ -75,28 +81,30 @@ code = '''
     }
 '''
 
-[[case]] # region corruption (causes cascading failures)
-define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
-define.LFS_ERASE_CYCLES = 0xffffffff
-define.LFS_ERASE_VALUE = [0x00, 0xff, -1]
-define.LFS_BADBLOCK_BEHAVIOR = [
+[cases.region_corruption] # (causes cascading failures)
+defines.BLOCK_COUNT = 256 # small bd so test runs faster
+defines.ERASE_CYCLES = 0xffffffff
+defines.ERASE_VALUE = [0x00, 0xff, -1]
+defines.BADBLOCK_BEHAVIOR = [
     'LFS_TESTBD_BADBLOCK_PROGERROR',
     'LFS_TESTBD_BADBLOCK_ERASEERROR',
     'LFS_TESTBD_BADBLOCK_READERROR',
     'LFS_TESTBD_BADBLOCK_PROGNOOP',
     'LFS_TESTBD_BADBLOCK_ERASENOOP',
 ]
-define.NAMEMULT = 64
-define.FILEMULT = 1
+defines.NAMEMULT = 64
+defines.FILEMULT = 1
 code = '''
-    for (lfs_block_t i = 0; i < (LFS_BLOCK_COUNT-2)/2; i++) {
-        lfs_testbd_setwear(&cfg, i+2, 0xffffffff) => 0;
+    for (lfs_block_t i = 0; i < (BLOCK_COUNT-2)/2; i++) {
+        lfs_testbd_setwear(cfg, i+2, 0xffffffff) => 0;
     }
-    
-    lfs_format(&lfs, &cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 1; i < 10; i++) {
+        uint8_t buffer[1024];
         for (int j = 0; j < NAMEMULT; j++) {
             buffer[j] = '0'+i;
         }
@@ -108,10 +116,11 @@ code = '''
             buffer[j+NAMEMULT+1] = '0'+i;
         }
         buffer[2*NAMEMULT+1] = '\0';
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, (char*)buffer,
                 LFS_O_WRONLY | LFS_O_CREAT) => 0;
         
-        size = NAMEMULT;
+        lfs_size_t size = NAMEMULT;
         for (int j = 0; j < i*FILEMULT; j++) {
             lfs_file_write(&lfs, &file, buffer, size) => size;
         }
@@ -120,12 +129,14 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 1; i < 10; i++) {
+        uint8_t buffer[1024];
         for (int j = 0; j < NAMEMULT; j++) {
             buffer[j] = '0'+i;
         }
         buffer[NAMEMULT] = '\0';
+        struct lfs_info info;
         lfs_stat(&lfs, (char*)buffer, &info) => 0;
         info.type => LFS_TYPE_DIR;
 
@@ -134,9 +145,10 @@ code = '''
             buffer[j+NAMEMULT+1] = '0'+i;
         }
         buffer[2*NAMEMULT+1] = '\0';
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0;
         
-        size = NAMEMULT;
+        lfs_size_t size = NAMEMULT;
         for (int j = 0; j < i*FILEMULT; j++) {
             uint8_t rbuffer[1024];
             lfs_file_read(&lfs, &file, rbuffer, size) => size;
@@ -148,28 +160,30 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # alternating corruption (causes cascading failures)
-define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
-define.LFS_ERASE_CYCLES = 0xffffffff
-define.LFS_ERASE_VALUE = [0x00, 0xff, -1]
-define.LFS_BADBLOCK_BEHAVIOR = [
+[cases.alternating_corruption] # (causes cascading failures)
+defines.BLOCK_COUNT = 256 # small bd so test runs faster
+defines.ERASE_CYCLES = 0xffffffff
+defines.ERASE_VALUE = [0x00, 0xff, -1]
+defines.BADBLOCK_BEHAVIOR = [
     'LFS_TESTBD_BADBLOCK_PROGERROR',
     'LFS_TESTBD_BADBLOCK_ERASEERROR',
     'LFS_TESTBD_BADBLOCK_READERROR',
     'LFS_TESTBD_BADBLOCK_PROGNOOP',
     'LFS_TESTBD_BADBLOCK_ERASENOOP',
 ]
-define.NAMEMULT = 64
-define.FILEMULT = 1
+defines.NAMEMULT = 64
+defines.FILEMULT = 1
 code = '''
-    for (lfs_block_t i = 0; i < (LFS_BLOCK_COUNT-2)/2; i++) {
-        lfs_testbd_setwear(&cfg, (2*i) + 2, 0xffffffff) => 0;
+    for (lfs_block_t i = 0; i < (BLOCK_COUNT-2)/2; i++) {
+        lfs_testbd_setwear(cfg, (2*i) + 2, 0xffffffff) => 0;
     }
-    
-    lfs_format(&lfs, &cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 1; i < 10; i++) {
+        uint8_t buffer[1024];
         for (int j = 0; j < NAMEMULT; j++) {
             buffer[j] = '0'+i;
         }
@@ -181,10 +195,11 @@ code = '''
             buffer[j+NAMEMULT+1] = '0'+i;
         }
         buffer[2*NAMEMULT+1] = '\0';
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, (char*)buffer,
                 LFS_O_WRONLY | LFS_O_CREAT) => 0;
         
-        size = NAMEMULT;
+        lfs_size_t size = NAMEMULT;
         for (int j = 0; j < i*FILEMULT; j++) {
             lfs_file_write(&lfs, &file, buffer, size) => size;
         }
@@ -193,12 +208,14 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 1; i < 10; i++) {
+        uint8_t buffer[1024];
         for (int j = 0; j < NAMEMULT; j++) {
             buffer[j] = '0'+i;
         }
         buffer[NAMEMULT] = '\0';
+        struct lfs_info info;
         lfs_stat(&lfs, (char*)buffer, &info) => 0;
         info.type => LFS_TYPE_DIR;
 
@@ -207,9 +224,10 @@ code = '''
             buffer[j+NAMEMULT+1] = '0'+i;
         }
         buffer[2*NAMEMULT+1] = '\0';
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0;
         
-        size = NAMEMULT;
+        lfs_size_t size = NAMEMULT;
         for (int j = 0; j < i*FILEMULT; j++) {
             uint8_t rbuffer[1024];
             lfs_file_read(&lfs, &file, rbuffer, size) => size;
@@ -222,10 +240,10 @@ code = '''
 '''
 
 # other corner cases
-[[case]] # bad superblocks (corrupt 1 or 0)
-define.LFS_ERASE_CYCLES = 0xffffffff
-define.LFS_ERASE_VALUE = [0x00, 0xff, -1]
-define.LFS_BADBLOCK_BEHAVIOR = [
+[cases.bad_superblocks] # (corrupt 1 or 0)
+defines.ERASE_CYCLES = 0xffffffff
+defines.ERASE_VALUE = [0x00, 0xff, -1]
+defines.BADBLOCK_BEHAVIOR = [
     'LFS_TESTBD_BADBLOCK_PROGERROR',
     'LFS_TESTBD_BADBLOCK_ERASEERROR',
     'LFS_TESTBD_BADBLOCK_READERROR',
@@ -233,9 +251,10 @@ define.LFS_BADBLOCK_BEHAVIOR = [
     'LFS_TESTBD_BADBLOCK_ERASENOOP',
 ]
 code = '''
-    lfs_testbd_setwear(&cfg, 0, 0xffffffff) => 0;
-    lfs_testbd_setwear(&cfg, 1, 0xffffffff) => 0;
+    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;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => LFS_ERR_NOSPC;
+    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
 '''

+ 180 - 87
tests/test_dirs.toml

@@ -1,8 +1,11 @@
-[[case]] # root
+[cases.root]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -14,20 +17,25 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # many directory creation
-define.N = 'range(0, 100, 3)'
+[cases.many_dir_creation]
+defines.N = [3,6,9,12,21,33,57,66,72,93,99]
+if = 'N < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "dir%03d", i);
         lfs_mkdir(&lfs, path) => 0;
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -35,6 +43,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "dir%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_DIR);
@@ -45,20 +54,25 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # many directory removal
-define.N = 'range(3, 100, 11)'
+[cases.many_dir_removal]
+defines.N = [3,6,9,12,21,33,57,66,72,93,99]
+if = 'N < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "removeme%03d", i);
         lfs_mkdir(&lfs, path) => 0;
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -66,6 +80,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "removeme%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_DIR);
@@ -75,14 +90,15 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "removeme%03d", i);
         lfs_remove(&lfs, path) => 0;
     }
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
@@ -95,20 +111,25 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # many directory rename
-define.N = 'range(3, 100, 11)'
+[cases.many_dir_rename]
+defines.N = [3,6,9,12,21,33,57,66,72,93,99]
+if = 'N < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "test%03d", i);
         lfs_mkdir(&lfs, path) => 0;
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -116,6 +137,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "test%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_DIR);
@@ -125,7 +147,7 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
         char oldpath[128];
         char newpath[128];
@@ -135,7 +157,7 @@ code = '''
     }
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
@@ -144,6 +166,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "tedd%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_DIR);
@@ -154,29 +177,34 @@ code = '''
     lfs_unmount(&lfs);
 '''
 
-[[case]] # reentrant many directory creation/rename/removal
-define.N = [5, 11]
+[cases.reentrant_many_dir]
+defines.N = [5, 11]
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hi%03d", i);
         err = lfs_mkdir(&lfs, path);
         assert(err == 0 || err == LFS_ERR_EXIST);
     }
 
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hello%03d", i);
         err = lfs_remove(&lfs, path);
         assert(err == 0 || err == LFS_ERR_NOENT);
     }
 
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -184,6 +212,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hi%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_DIR);
@@ -209,6 +238,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hello%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_DIR);
@@ -218,6 +248,7 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
 
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hello%03d", i);
         lfs_remove(&lfs, path) => 0;
     }
@@ -234,22 +265,28 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # file creation
-define.N = 'range(3, 100, 11)'
+[cases.file_creation]
+defines.N = [3,6,9,12,21,33,57,66,72,93,99]
+if = 'N < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "file%03d", i);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         lfs_file_close(&lfs, &file) => 0;
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -257,6 +294,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "file%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_REG);
@@ -267,22 +305,28 @@ code = '''
     lfs_unmount(&lfs);
 '''
 
-[[case]] # file removal
-define.N = 'range(0, 100, 3)'
+[cases.file_removal]
+defines.N = [3,6,9,12,21,33,57,66,72,93,99]
+if = 'N < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "removeme%03d", i);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         lfs_file_close(&lfs, &file) => 0;
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -290,6 +334,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "removeme%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_REG);
@@ -299,14 +344,15 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "removeme%03d", i);
         lfs_remove(&lfs, path) => 0;
     }
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
@@ -319,22 +365,28 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # file rename
-define.N = 'range(0, 100, 3)'
+[cases.file_rename]
+defines.N = [3,6,9,12,21,33,57,66,72,93,99]
+if = 'N < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "test%03d", i);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         lfs_file_close(&lfs, &file) => 0;
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -342,6 +394,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "test%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_REG);
@@ -351,7 +404,7 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
         char oldpath[128];
         char newpath[128];
@@ -361,7 +414,7 @@ code = '''
     }
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
@@ -370,6 +423,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "tedd%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_REG);
@@ -380,29 +434,36 @@ code = '''
     lfs_unmount(&lfs);
 '''
 
-[[case]] # reentrant file creation/rename/removal
-define.N = [5, 25]
+[cases.reentrant_files]
+defines.N = [5, 25]
+if = 'N < BLOCK_COUNT/2'
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hi%03d", i);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0;
         lfs_file_close(&lfs, &file) => 0;
     }
 
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hello%03d", i);
         err = lfs_remove(&lfs, path);
         assert(err == 0 || err == LFS_ERR_NOENT);
     }
 
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -410,6 +471,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hi%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_REG);
@@ -435,6 +497,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hello%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_REG);
@@ -444,6 +507,7 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
 
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "hello%03d", i);
         lfs_remove(&lfs, path) => 0;
     }
@@ -460,24 +524,28 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # nested directories
+[cases.nested_dirs]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "potato") => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "burito",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "potato/baked") => 0;
     lfs_mkdir(&lfs, "potato/sweet") => 0;
     lfs_mkdir(&lfs, "potato/fried") => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "potato") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(strcmp(info.name, ".") == 0);
     info.type => LFS_TYPE_DIR;
@@ -498,21 +566,21 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // try removing?
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_remove(&lfs, "potato") => LFS_ERR_NOTEMPTY;
     lfs_unmount(&lfs) => 0;
 
     // try renaming?
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_rename(&lfs, "potato", "coldpotato") => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_rename(&lfs, "coldpotato", "warmpotato") => 0;
     lfs_rename(&lfs, "warmpotato", "hotpotato") => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_remove(&lfs, "potato") => LFS_ERR_NOENT;
     lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOENT;
     lfs_remove(&lfs, "warmpotato") => LFS_ERR_NOENT;
@@ -520,7 +588,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // try cross-directory renaming
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "coldpotato") => 0;
     lfs_rename(&lfs, "hotpotato/baked", "coldpotato/baked") => 0;
     lfs_rename(&lfs, "coldpotato", "hotpotato") => LFS_ERR_NOTEMPTY;
@@ -536,7 +604,7 @@ code = '''
     lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_dir_open(&lfs, &dir, "hotpotato") => 0;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(strcmp(info.name, ".") == 0);
@@ -558,7 +626,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
     
     // final remove
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
     lfs_remove(&lfs, "hotpotato/baked") => 0;
     lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
@@ -568,7 +636,7 @@ code = '''
     lfs_remove(&lfs, "hotpotato") => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(strcmp(info.name, ".") == 0);
@@ -584,17 +652,22 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # recursive remove
-define.N = [10, 100]
+[cases.recursive_remove]
+defines.N = [10, 100]
+if = 'N < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "prickly-pear") => 0;
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "prickly-pear/cactus%03d", i);
         lfs_mkdir(&lfs, path) => 0;
     }
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -602,6 +675,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "cactus%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_DIR);
@@ -611,7 +685,7 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY;
 
     lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
@@ -622,6 +696,7 @@ code = '''
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, "..") == 0);
     for (int i = 0; i < N; i++) {
+        char path[1024];
         sprintf(path, "cactus%03d", i);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(info.type == LFS_TYPE_DIR);
@@ -636,22 +711,24 @@ code = '''
     lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # other error cases
+[cases.other_errors]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "potato") => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "burito",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     lfs_mkdir(&lfs, "potato") => LFS_ERR_EXIST;
     lfs_mkdir(&lfs, "burito") => LFS_ERR_EXIST;
@@ -659,6 +736,7 @@ code = '''
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
     lfs_file_open(&lfs, &file, "potato",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "tomato") => LFS_ERR_NOENT;
     lfs_dir_open(&lfs, &dir, "burito") => LFS_ERR_NOTDIR;
     lfs_file_open(&lfs, &file, "tomato", LFS_O_RDONLY) => LFS_ERR_NOENT;
@@ -678,6 +756,7 @@ code = '''
 
     // check that errors did not corrupt directory
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
     assert(strcmp(info.name, ".") == 0);
@@ -696,7 +775,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // or on disk
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(info.type == LFS_TYPE_DIR);
@@ -715,21 +794,26 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # directory seek
-define.COUNT = [4, 128, 132]
+[cases.directory_seek]
+defines.COUNT = [4, 128, 132]
+if = 'COUNT < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "hello") => 0;
     for (int i = 0; i < COUNT; i++) {
+        char path[1024];
         sprintf(path, "hello/kitty%03d", i);
         lfs_mkdir(&lfs, path) => 0;
     }
     lfs_unmount(&lfs) => 0;
 
     for (int j = 2; j < COUNT; j++) {
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
+        lfs_dir_t dir;
         lfs_dir_open(&lfs, &dir, "hello") => 0;
+        struct lfs_info info;
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(strcmp(info.name, ".") == 0);
         assert(info.type == LFS_TYPE_DIR);
@@ -739,6 +823,7 @@ code = '''
 
         lfs_soff_t pos;
         for (int i = 0; i < j; i++) {
+            char path[1024];
             sprintf(path, "kitty%03d", i);
             lfs_dir_read(&lfs, &dir, &info) => 1;
             assert(strcmp(info.name, path) == 0);
@@ -748,13 +833,14 @@ code = '''
         }
 
         lfs_dir_seek(&lfs, &dir, pos) => 0;
+        char path[1024];
         sprintf(path, "kitty%03d", j);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(strcmp(info.name, path) == 0);
         assert(info.type == LFS_TYPE_DIR);
 
         lfs_dir_rewind(&lfs, &dir) => 0;
-        sprintf(path, "kitty%03d", 0);
+        sprintf(path, "kitty%03u", 0);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(strcmp(info.name, ".") == 0);
         assert(info.type == LFS_TYPE_DIR);
@@ -776,20 +862,25 @@ code = '''
     }
 '''
 
-[[case]] # root seek
-define.COUNT = [4, 128, 132]
+[cases.root_seek]
+defines.COUNT = [4, 128, 132]
+if = 'COUNT < BLOCK_COUNT/2'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < COUNT; i++) {
+        char path[1024];
         sprintf(path, "hi%03d", i);
         lfs_mkdir(&lfs, path) => 0;
     }
     lfs_unmount(&lfs) => 0;
 
     for (int j = 2; j < COUNT; j++) {
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
+        lfs_dir_t dir;
         lfs_dir_open(&lfs, &dir, "/") => 0;
+        struct lfs_info info;
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(strcmp(info.name, ".") == 0);
         assert(info.type == LFS_TYPE_DIR);
@@ -799,6 +890,7 @@ code = '''
 
         lfs_soff_t pos;
         for (int i = 0; i < j; i++) {
+            char path[1024];
             sprintf(path, "hi%03d", i);
             lfs_dir_read(&lfs, &dir, &info) => 1;
             assert(strcmp(info.name, path) == 0);
@@ -808,13 +900,14 @@ code = '''
         }
 
         lfs_dir_seek(&lfs, &dir, pos) => 0;
+        char path[1024];
         sprintf(path, "hi%03d", j);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(strcmp(info.name, path) == 0);
         assert(info.type == LFS_TYPE_DIR);
 
         lfs_dir_rewind(&lfs, &dir) => 0;
-        sprintf(path, "hi%03d", 0);
+        sprintf(path, "hi%03u", 0);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(strcmp(info.name, ".") == 0);
         assert(info.type == LFS_TYPE_DIR);

+ 59 - 28
tests/test_entries.toml

@@ -2,19 +2,23 @@
 # Note that these tests are intended for 512 byte inline sizes. They should
 # still pass with other inline sizes but wouldn't be testing anything.
 
-define.LFS_CACHE_SIZE = 512
-if = 'LFS_CACHE_SIZE % LFS_PROG_SIZE == 0 && LFS_CACHE_SIZE == 512'
+defines.CACHE_SIZE = 512
+if = 'CACHE_SIZE % PROG_SIZE == 0 && CACHE_SIZE == 512'
 
-[[case]] # entry grow test
+[cases.entry_grow]
 code = '''
     uint8_t wbuffer[1024];
     uint8_t rbuffer[1024];
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // write hi0 20
+    char path[1024];
+    lfs_size_t size;
     sprintf(path, "hi0"); size = 20;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     memset(wbuffer, 'c', size);
@@ -94,16 +98,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # entry shrink test
+[cases.entry_shrink]
 code = '''
     uint8_t wbuffer[1024];
     uint8_t rbuffer[1024];
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // write hi0 20
+    char path[1024];
+    lfs_size_t size;
     sprintf(path, "hi0"); size = 20;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     memset(wbuffer, 'c', size);
@@ -183,16 +191,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # entry spill test
+[cases.entry_spill]
 code = '''
     uint8_t wbuffer[1024];
     uint8_t rbuffer[1024];
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // write hi0 200
+    char path[1024];
+    lfs_size_t size;
     sprintf(path, "hi0"); size = 200;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     memset(wbuffer, 'c', size);
@@ -256,16 +268,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # entry push spill test
+[cases.entry_push_spill]
 code = '''
     uint8_t wbuffer[1024];
     uint8_t rbuffer[1024];
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // write hi0 200
+    char path[1024];
+    lfs_size_t size;
     sprintf(path, "hi0"); size = 200;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     memset(wbuffer, 'c', size);
@@ -345,16 +361,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # entry push spill two test
+[cases.entry_push_spill_two]
 code = '''
     uint8_t wbuffer[1024];
     uint8_t rbuffer[1024];
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // write hi0 200
+    char path[1024];
+    lfs_size_t size;
     sprintf(path, "hi0"); size = 200;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     memset(wbuffer, 'c', size);
@@ -449,16 +469,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # entry drop test
+[cases.entry_drop]
 code = '''
     uint8_t wbuffer[1024];
     uint8_t rbuffer[1024];
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     // write hi0 200
+    char path[1024];
+    lfs_size_t size;
     sprintf(path, "hi0"); size = 200;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     memset(wbuffer, 'c', size);
@@ -491,6 +515,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
 
     lfs_remove(&lfs, "hi1") => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "hi1", &info) => LFS_ERR_NOENT;
     // read hi0 200
     sprintf(path, "hi0"); size = 200;
@@ -547,15 +572,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # create too big
+[cases.create_too_big]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    char path[1024];
     memset(path, 'm', 200);
     path[200] = '\0';
 
-    size = 400;
+    lfs_size_t size = 400;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     uint8_t wbuffer[1024];
@@ -572,15 +600,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # resize too big
+[cases.resize_too_big]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    char path[1024];
     memset(path, 'm', 200);
     path[200] = '\0';
 
-    size = 40;
+    lfs_size_t size = 40;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     uint8_t wbuffer[1024];

+ 67 - 49
tests/test_evil.toml

@@ -3,16 +3,17 @@
 
 # invalid pointer tests (outside of block_count)
 
-[[case]] # invalid tail-pointer test
-define.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL']
-define.INVALSET = [0x3, 0x1, 0x2]
+[cases.invalid_tail_pointer]
+defines.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL']
+defines.INVALSET = [0x3, 0x1, 0x2]
 in = "lfs.c"
 code = '''
     // create littlefs
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     // change tail-pointer to invalid pointers
-    lfs_init(&lfs, &cfg) => 0;
+    lfs_init(&lfs, cfg) => 0;
     lfs_mdir_t mdir;
     lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
     lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
@@ -23,25 +24,27 @@ code = '''
     lfs_deinit(&lfs) => 0;
 
     // test that mount fails gracefully
-    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
+    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
 '''
 
-[[case]] # invalid dir pointer test
-define.INVALSET = [0x3, 0x1, 0x2]
+[cases.invalid_dir_pointer]
+defines.INVALSET = [0x3, 0x1, 0x2]
 in = "lfs.c"
 code = '''
     // create littlefs
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
     // make a dir
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "dir_here") => 0;
     lfs_unmount(&lfs) => 0;
 
     // change the dir pointer to be invalid
-    lfs_init(&lfs, &cfg) => 0;
+    lfs_init(&lfs, cfg) => 0;
     lfs_mdir_t mdir;
     lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
     // make sure id 1 == our directory
+    uint8_t buffer[1024];
     lfs_dir_get(&lfs, &mdir,
             LFS_MKTAG(0x700, 0x3ff, 0),
             LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("dir_here")), buffer)
@@ -57,14 +60,17 @@ code = '''
 
     // test that accessing our bad dir fails, note there's a number
     // of ways to access the dir, some can fail, but some don't
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "dir_here", &info) => 0;
     assert(strcmp(info.name, "dir_here") == 0);
     assert(info.type == LFS_TYPE_DIR);
 
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "dir_here") => LFS_ERR_CORRUPT;
     lfs_stat(&lfs, "dir_here/file_here", &info) => LFS_ERR_CORRUPT;
     lfs_dir_open(&lfs, &dir, "dir_here/dir_here") => LFS_ERR_CORRUPT;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "dir_here/file_here",
             LFS_O_RDONLY) => LFS_ERR_CORRUPT;
     lfs_file_open(&lfs, &file, "dir_here/file_here",
@@ -72,24 +78,27 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # invalid file pointer test
+[cases.invalid_file_pointer]
 in = "lfs.c"
-define.SIZE = [10, 1000, 100000] # faked file size
+defines.SIZE = [10, 1000, 100000] # faked file size
 code = '''
     // create littlefs
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
     // make a file
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "file_here",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
     // change the file pointer to be invalid
-    lfs_init(&lfs, &cfg) => 0;
+    lfs_init(&lfs, cfg) => 0;
     lfs_mdir_t mdir;
     lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
     // make sure id 1 == our file
+    uint8_t buffer[1024];
     lfs_dir_get(&lfs, &mdir,
             LFS_MKTAG(0x700, 0x3ff, 0),
             LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
@@ -103,7 +112,8 @@ code = '''
 
     // test that accessing our bad file fails, note there's a number
     // of ways to access the dir, some can fail, but some don't
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "file_here", &info) => 0;
     assert(strcmp(info.name, "file_here") == 0);
     assert(info.type == LFS_TYPE_REG);
@@ -114,20 +124,22 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
 
     // any allocs that traverse CTZ must unfortunately must fail
-    if (SIZE > 2*LFS_BLOCK_SIZE) {
+    if (SIZE > 2*BLOCK_SIZE) {
         lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
     }
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # invalid pointer in CTZ skip-list test
-define.SIZE = ['2*LFS_BLOCK_SIZE', '3*LFS_BLOCK_SIZE', '4*LFS_BLOCK_SIZE']
+[cases.invalid_ctz_pointer] # invalid pointer in CTZ skip-list test
+defines.SIZE = ['2*BLOCK_SIZE', '3*BLOCK_SIZE', '4*BLOCK_SIZE']
 in = "lfs.c"
 code = '''
     // create littlefs
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
     // make a file
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "file_here",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
     for (int i = 0; i < SIZE; i++) {
@@ -137,10 +149,11 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
     // change pointer in CTZ skip-list to be invalid
-    lfs_init(&lfs, &cfg) => 0;
+    lfs_init(&lfs, cfg) => 0;
     lfs_mdir_t mdir;
     lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
     // make sure id 1 == our file and get our CTZ structure
+    uint8_t buffer[4*BLOCK_SIZE];
     lfs_dir_get(&lfs, &mdir,
             LFS_MKTAG(0x700, 0x3ff, 0),
             LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
@@ -153,18 +166,19 @@ code = '''
                 => LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz));
     lfs_ctz_fromle32(&ctz);
     // rewrite block to contain bad pointer
-    uint8_t bbuffer[LFS_BLOCK_SIZE];
-    cfg.read(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    uint8_t bbuffer[BLOCK_SIZE];
+    cfg->read(cfg, ctz.head, 0, bbuffer, BLOCK_SIZE) => 0;
     uint32_t bad = lfs_tole32(0xcccccccc);
     memcpy(&bbuffer[0], &bad, sizeof(bad));
     memcpy(&bbuffer[4], &bad, sizeof(bad));
-    cfg.erase(&cfg, ctz.head) => 0;
-    cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
+    cfg->erase(cfg, ctz.head) => 0;
+    cfg->prog(cfg, ctz.head, 0, bbuffer, BLOCK_SIZE) => 0;
     lfs_deinit(&lfs) => 0;
 
     // test that accessing our bad file fails, note there's a number
     // of ways to access the dir, some can fail, but some don't
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "file_here", &info) => 0;
     assert(strcmp(info.name, "file_here") == 0);
     assert(info.type == LFS_TYPE_REG);
@@ -175,22 +189,23 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
 
     // any allocs that traverse CTZ must unfortunately must fail
-    if (SIZE > 2*LFS_BLOCK_SIZE) {
+    if (SIZE > 2*BLOCK_SIZE) {
         lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
     }
     lfs_unmount(&lfs) => 0;
 '''
 
 
-[[case]] # invalid gstate pointer
-define.INVALSET = [0x3, 0x1, 0x2]
+[cases.invalid_gstate_pointer]
+defines.INVALSET = [0x3, 0x1, 0x2]
 in = "lfs.c"
 code = '''
     // create littlefs
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     // create an invalid gstate
-    lfs_init(&lfs, &cfg) => 0;
+    lfs_init(&lfs, cfg) => 0;
     lfs_mdir_t mdir;
     lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
     lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){
@@ -202,21 +217,22 @@ code = '''
     // test that mount fails gracefully
     // mount may not fail, but our first alloc should fail when
     // we try to fix the gstate
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "should_fail") => LFS_ERR_CORRUPT;
     lfs_unmount(&lfs) => 0;
 '''
 
 # cycle detection/recovery tests
 
-[[case]] # metadata-pair threaded-list loop test
+[cases.mdir_loop] # metadata-pair threaded-list loop test
 in = "lfs.c"
 code = '''
     // create littlefs
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     // change tail-pointer to point to ourself
-    lfs_init(&lfs, &cfg) => 0;
+    lfs_init(&lfs, cfg) => 0;
     lfs_mdir_t mdir;
     lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
     lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
@@ -225,20 +241,21 @@ code = '''
     lfs_deinit(&lfs) => 0;
 
     // test that mount fails gracefully
-    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
+    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
 '''
 
-[[case]] # metadata-pair threaded-list 2-length loop test
+[cases.mdir_loop_2] # metadata-pair threaded-list 2-length loop test
 in = "lfs.c"
 code = '''
     // create littlefs with child dir
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "child") => 0;
     lfs_unmount(&lfs) => 0;
 
     // find child
-    lfs_init(&lfs, &cfg) => 0;
+    lfs_init(&lfs, cfg) => 0;
     lfs_mdir_t mdir;
     lfs_block_t pair[2];
     lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
@@ -255,20 +272,21 @@ code = '''
     lfs_deinit(&lfs) => 0;
 
     // test that mount fails gracefully
-    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
+    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
 '''
 
-[[case]] # metadata-pair threaded-list 1-length child loop test
+[cases.mdir_loop_child] # metadata-pair threaded-list 1-length child loop test
 in = "lfs.c"
 code = '''
     // create littlefs with child dir
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "child") => 0;
     lfs_unmount(&lfs) => 0;
 
     // find child
-    lfs_init(&lfs, &cfg) => 0;
+    lfs_init(&lfs, cfg) => 0;
     lfs_mdir_t mdir;
     lfs_block_t pair[2];
     lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
@@ -284,5 +302,5 @@ code = '''
     lfs_deinit(&lfs) => 0;
 
     // test that mount fails gracefully
-    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
+    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
 '''

+ 122 - 82
tests/test_exhaustion.toml

@@ -1,30 +1,34 @@
-[[case]] # test running a filesystem to exhaustion
-define.LFS_ERASE_CYCLES = 10
-define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
-define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
-define.LFS_BADBLOCK_BEHAVIOR = [
+# test running a filesystem to exhaustion
+[cases.exhaustion]
+defines.ERASE_CYCLES = 10
+defines.BLOCK_COUNT = 256 # small bd so test runs faster
+defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
+defines.BADBLOCK_BEHAVIOR = [
     'LFS_TESTBD_BADBLOCK_PROGERROR',
     'LFS_TESTBD_BADBLOCK_ERASEERROR',
     'LFS_TESTBD_BADBLOCK_READERROR',
     'LFS_TESTBD_BADBLOCK_PROGNOOP',
     'LFS_TESTBD_BADBLOCK_ERASENOOP',
 ]
-define.FILES = 10
+defines.FILES = 10
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    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;
+        lfs_mount(&lfs, cfg) => 0;
         for (uint32_t i = 0; i < FILES; i++) {
             // chose name, roughly random seed, and random 2^n size
+            char path[1024];
             sprintf(path, "roadrunner/test%d", i);
             srand(cycle * i);
-            size = 1 << ((rand() % 10)+2);
+            lfs_size_t size = 1 << ((rand() % 10)+2);
 
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path,
                     LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 
@@ -33,14 +37,14 @@ code = '''
                 lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                 assert(res == 1 || res == LFS_ERR_NOSPC);
                 if (res == LFS_ERR_NOSPC) {
-                    err = lfs_file_close(&lfs, &file);
+                    int err = lfs_file_close(&lfs, &file);
                     assert(err == 0 || err == LFS_ERR_NOSPC);
                     lfs_unmount(&lfs) => 0;
                     goto exhausted;
                 }
             }
 
-            err = lfs_file_close(&lfs, &file);
+            int err = lfs_file_close(&lfs, &file);
             assert(err == 0 || err == LFS_ERR_NOSPC);
             if (err == LFS_ERR_NOSPC) {
                 lfs_unmount(&lfs) => 0;
@@ -50,10 +54,12 @@ code = '''
 
         for (uint32_t i = 0; i < FILES; i++) {
             // check for errors
+            char path[1024];
             sprintf(path, "roadrunner/test%d", i);
             srand(cycle * i);
-            size = 1 << ((rand() % 10)+2);
+            lfs_size_t size = 1 << ((rand() % 10)+2);
 
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
             for (lfs_size_t j = 0; j < size; j++) {
                 char c = 'a' + (rand() % 26);
@@ -71,10 +77,12 @@ code = '''
 
 exhausted:
     // should still be readable
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (uint32_t i = 0; i < FILES; i++) {
         // check for errors
+        char path[1024];
         sprintf(path, "roadrunner/test%d", i);
+        struct lfs_info info;
         lfs_stat(&lfs, path, &info) => 0;
     }
     lfs_unmount(&lfs) => 0;
@@ -82,31 +90,35 @@ exhausted:
     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 test runs faster
-define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
-define.LFS_BADBLOCK_BEHAVIOR = [
+# test running a filesystem to exhaustion
+# which also requires expanding superblocks
+[cases.exhaustion_superblocks]
+defines.ERASE_CYCLES = 10
+defines.BLOCK_COUNT = 256 # small bd so test runs faster
+defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
+defines.BADBLOCK_BEHAVIOR = [
     'LFS_TESTBD_BADBLOCK_PROGERROR',
     'LFS_TESTBD_BADBLOCK_ERASEERROR',
     'LFS_TESTBD_BADBLOCK_READERROR',
     'LFS_TESTBD_BADBLOCK_PROGNOOP',
     'LFS_TESTBD_BADBLOCK_ERASENOOP',
 ]
-define.FILES = 10
+defines.FILES = 10
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     uint32_t cycle = 0;
     while (true) {
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (uint32_t i = 0; i < FILES; i++) {
             // chose name, roughly random seed, and random 2^n size
+            char path[1024];
             sprintf(path, "test%d", i);
             srand(cycle * i);
-            size = 1 << ((rand() % 10)+2);
+            lfs_size_t size = 1 << ((rand() % 10)+2);
 
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path,
                     LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 
@@ -115,14 +127,14 @@ code = '''
                 lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                 assert(res == 1 || res == LFS_ERR_NOSPC);
                 if (res == LFS_ERR_NOSPC) {
-                    err = lfs_file_close(&lfs, &file);
+                    int err = lfs_file_close(&lfs, &file);
                     assert(err == 0 || err == LFS_ERR_NOSPC);
                     lfs_unmount(&lfs) => 0;
                     goto exhausted;
                 }
             }
 
-            err = lfs_file_close(&lfs, &file);
+            int err = lfs_file_close(&lfs, &file);
             assert(err == 0 || err == LFS_ERR_NOSPC);
             if (err == LFS_ERR_NOSPC) {
                 lfs_unmount(&lfs) => 0;
@@ -132,10 +144,12 @@ code = '''
 
         for (uint32_t i = 0; i < FILES; i++) {
             // check for errors
+            char path[1024];
             sprintf(path, "test%d", i);
             srand(cycle * i);
-            size = 1 << ((rand() % 10)+2);
+            lfs_size_t size = 1 << ((rand() % 10)+2);
 
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
             for (lfs_size_t j = 0; j < size; j++) {
                 char c = 'a' + (rand() % 26);
@@ -153,9 +167,11 @@ code = '''
 
 exhausted:
     // should still be readable
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (uint32_t i = 0; i < FILES; i++) {
         // check for errors
+        char path[1024];
+        struct lfs_info info;
         sprintf(path, "test%d", i);
         lfs_stat(&lfs, path, &info) => 0;
     }
@@ -169,35 +185,39 @@ exhausted:
 # 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 = 20
-define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
-define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
-define.FILES = 10
+# wear-level test running a filesystem to exhaustion
+[cases.wear_leveling_exhaustion]
+defines.ERASE_CYCLES = 20
+defines.BLOCK_COUNT = 256 # small bd so test runs faster
+defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
+defines.FILES = 10
 code = '''
     uint32_t run_cycles[2];
-    const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT};
+    const uint32_t run_block_count[2] = {BLOCK_COUNT/2, 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;
+        for (lfs_block_t b = 0; b < BLOCK_COUNT; b++) {
+            lfs_testbd_setwear(cfg, b,
+                    (b < run_block_count[run]) ? 0 : ERASE_CYCLES) => 0;
         }
 
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_t lfs;
+        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;
+            lfs_mount(&lfs, cfg) => 0;
             for (uint32_t i = 0; i < FILES; i++) {
                 // chose name, roughly random seed, and random 2^n size
+                char path[1024];
                 sprintf(path, "roadrunner/test%d", i);
                 srand(cycle * i);
-                size = 1 << ((rand() % 10)+2);
+                lfs_size_t size = 1 << ((rand() % 10)+2);
 
+                lfs_file_t file;
                 lfs_file_open(&lfs, &file, path,
                         LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 
@@ -206,14 +226,14 @@ code = '''
                     lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                     assert(res == 1 || res == LFS_ERR_NOSPC);
                     if (res == LFS_ERR_NOSPC) {
-                        err = lfs_file_close(&lfs, &file);
+                        int err = lfs_file_close(&lfs, &file);
                         assert(err == 0 || err == LFS_ERR_NOSPC);
                         lfs_unmount(&lfs) => 0;
                         goto exhausted;
                     }
                 }
 
-                err = lfs_file_close(&lfs, &file);
+                int err = lfs_file_close(&lfs, &file);
                 assert(err == 0 || err == LFS_ERR_NOSPC);
                 if (err == LFS_ERR_NOSPC) {
                     lfs_unmount(&lfs) => 0;
@@ -223,10 +243,12 @@ code = '''
 
             for (uint32_t i = 0; i < FILES; i++) {
                 // check for errors
+                char path[1024];
                 sprintf(path, "roadrunner/test%d", i);
                 srand(cycle * i);
-                size = 1 << ((rand() % 10)+2);
+                lfs_size_t size = 1 << ((rand() % 10)+2);
 
+                lfs_file_t file;
                 lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
                 for (lfs_size_t j = 0; j < size; j++) {
                     char c = 'a' + (rand() % 26);
@@ -244,9 +266,11 @@ code = '''
 
 exhausted:
         // should still be readable
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (uint32_t i = 0; i < FILES; i++) {
             // check for errors
+            char path[1024];
+            struct lfs_info info;
             sprintf(path, "roadrunner/test%d", i);
             lfs_stat(&lfs, path, &info) => 0;
         }
@@ -261,32 +285,36 @@ exhausted:
     LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
 '''
 
-[[case]] # wear-level test + expanding superblock
-define.LFS_ERASE_CYCLES = 20
-define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
-define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
-define.FILES = 10
+# wear-level test + expanding superblock
+[cases.wear_leveling_exhaustion_superblocks]
+defines.ERASE_CYCLES = 20
+defines.BLOCK_COUNT = 256 # small bd so test runs faster
+defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
+defines.FILES = 10
 code = '''
     uint32_t run_cycles[2];
-    const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT};
+    const uint32_t run_block_count[2] = {BLOCK_COUNT/2, 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;
+        for (lfs_block_t b = 0; b < BLOCK_COUNT; b++) {
+            lfs_testbd_setwear(cfg, b,
+                    (b < run_block_count[run]) ? 0 : ERASE_CYCLES) => 0;
         }
 
-        lfs_format(&lfs, &cfg) => 0;
+        lfs_t lfs;
+        lfs_format(&lfs, cfg) => 0;
 
         uint32_t cycle = 0;
         while (true) {
-            lfs_mount(&lfs, &cfg) => 0;
+            lfs_mount(&lfs, cfg) => 0;
             for (uint32_t i = 0; i < FILES; i++) {
                 // chose name, roughly random seed, and random 2^n size
+                char path[1024];
                 sprintf(path, "test%d", i);
                 srand(cycle * i);
-                size = 1 << ((rand() % 10)+2);
+                lfs_size_t size = 1 << ((rand() % 10)+2);
 
+                lfs_file_t file;
                 lfs_file_open(&lfs, &file, path,
                         LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 
@@ -295,14 +323,14 @@ code = '''
                     lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                     assert(res == 1 || res == LFS_ERR_NOSPC);
                     if (res == LFS_ERR_NOSPC) {
-                        err = lfs_file_close(&lfs, &file);
+                        int err = lfs_file_close(&lfs, &file);
                         assert(err == 0 || err == LFS_ERR_NOSPC);
                         lfs_unmount(&lfs) => 0;
                         goto exhausted;
                     }
                 }
 
-                err = lfs_file_close(&lfs, &file);
+                int err = lfs_file_close(&lfs, &file);
                 assert(err == 0 || err == LFS_ERR_NOSPC);
                 if (err == LFS_ERR_NOSPC) {
                     lfs_unmount(&lfs) => 0;
@@ -312,10 +340,12 @@ code = '''
 
             for (uint32_t i = 0; i < FILES; i++) {
                 // check for errors
+                char path[1024];
                 sprintf(path, "test%d", i);
                 srand(cycle * i);
-                size = 1 << ((rand() % 10)+2);
+                lfs_size_t size = 1 << ((rand() % 10)+2);
 
+                lfs_file_t file;
                 lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
                 for (lfs_size_t j = 0; j < size; j++) {
                     char c = 'a' + (rand() % 26);
@@ -333,9 +363,11 @@ code = '''
 
 exhausted:
         // should still be readable
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (uint32_t i = 0; i < FILES; i++) {
             // check for errors
+            char path[1024];
+            struct lfs_info info;
             sprintf(path, "test%d", i);
             lfs_stat(&lfs, path, &info) => 0;
         }
@@ -350,28 +382,32 @@ exhausted:
     LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
 '''
 
-[[case]] # test that we wear blocks roughly evenly
-define.LFS_ERASE_CYCLES = 0xffffffff
-define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
-define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1]
-define.CYCLES = 100
-define.FILES = 10
-if = 'LFS_BLOCK_CYCLES < CYCLES/10'
+# test that we wear blocks roughly evenly
+[cases.wear_leveling_distribution]
+defines.ERASE_CYCLES = 0xffffffff
+defines.BLOCK_COUNT = 256 # small bd so test runs faster
+defines.BLOCK_CYCLES = [5, 4, 3, 2, 1]
+defines.CYCLES = 100
+defines.FILES = 10
+if = 'BLOCK_CYCLES < CYCLES/10'
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "roadrunner") => 0;
     lfs_unmount(&lfs) => 0;
 
     uint32_t cycle = 0;
     while (cycle < CYCLES) {
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         for (uint32_t i = 0; i < FILES; i++) {
             // chose name, roughly random seed, and random 2^n size
+            char path[1024];
             sprintf(path, "roadrunner/test%d", i);
             srand(cycle * i);
-            size = 1 << 4; //((rand() % 10)+2);
+            lfs_size_t size = 1 << 4; //((rand() % 10)+2);
 
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path,
                     LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 
@@ -380,14 +416,14 @@ code = '''
                 lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                 assert(res == 1 || res == LFS_ERR_NOSPC);
                 if (res == LFS_ERR_NOSPC) {
-                    err = lfs_file_close(&lfs, &file);
+                    int err = lfs_file_close(&lfs, &file);
                     assert(err == 0 || err == LFS_ERR_NOSPC);
                     lfs_unmount(&lfs) => 0;
                     goto exhausted;
                 }
             }
 
-            err = lfs_file_close(&lfs, &file);
+            int err = lfs_file_close(&lfs, &file);
             assert(err == 0 || err == LFS_ERR_NOSPC);
             if (err == LFS_ERR_NOSPC) {
                 lfs_unmount(&lfs) => 0;
@@ -397,10 +433,12 @@ code = '''
 
         for (uint32_t i = 0; i < FILES; i++) {
             // check for errors
+            char path[1024];
             sprintf(path, "roadrunner/test%d", i);
             srand(cycle * i);
-            size = 1 << 4; //((rand() % 10)+2);
+            lfs_size_t size = 1 << 4; //((rand() % 10)+2);
 
+            lfs_file_t file;
             lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
             for (lfs_size_t j = 0; j < size; j++) {
                 char c = 'a' + (rand() % 26);
@@ -418,9 +456,11 @@ code = '''
 
 exhausted:
     // should still be readable
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (uint32_t i = 0; i < FILES; i++) {
         // check for errors
+        char path[1024];
+        struct lfs_info info;
         sprintf(path, "roadrunner/test%d", i);
         lfs_stat(&lfs, path, &info) => 0;
     }
@@ -433,8 +473,8 @@ exhausted:
     lfs_testbd_wear_t totalwear = 0;
     lfs_testbd_wear_t maxwear = 0;
     // skip 0 and 1 as superblock movement is intentionally avoided
-    for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
-        lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
+    for (lfs_block_t b = 2; b < BLOCK_COUNT; b++) {
+        lfs_testbd_wear_t wear = lfs_testbd_getwear(cfg, b);
         printf("%08x: wear %d\n", b, wear);
         assert(wear >= 0);
         if (wear < minwear) {
@@ -445,15 +485,15 @@ exhausted:
         }
         totalwear += wear;
     }
-    lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT;
+    lfs_testbd_wear_t avgwear = totalwear / BLOCK_COUNT;
     LFS_WARN("max wear: %d cycles", maxwear);
-    LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT);
+    LFS_WARN("avg wear: %d cycles", totalwear / (int)BLOCK_COUNT);
     LFS_WARN("min wear: %d cycles", minwear);
 
     // find standard deviation^2
     lfs_testbd_wear_t dev2 = 0;
-    for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
-        lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
+    for (lfs_block_t b = 2; b < BLOCK_COUNT; b++) {
+        lfs_testbd_wear_t wear = lfs_testbd_getwear(cfg, b);
         assert(wear >= 0);
         lfs_testbd_swear_t diff = wear - avgwear;
         dev2 += diff*diff;

+ 102 - 72
tests/test_files.toml

@@ -1,17 +1,20 @@
 
-[[case]] # simple file test
+[cases.simple_file]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "hello",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
-    size = strlen("Hello World!")+1;
+    lfs_size_t size = strlen("Hello World!")+1;
+    uint8_t buffer[1024];
     strcpy((char*)buffer, "Hello World!");
     lfs_file_write(&lfs, &file, buffer, size) => size;
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
     lfs_file_read(&lfs, &file, buffer, size) => size;
     assert(strcmp((char*)buffer, "Hello World!") == 0);
@@ -19,17 +22,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # larger files
-define.SIZE = [32, 8192, 262144, 0, 7, 8193]
-define.CHUNKSIZE = [31, 16, 33, 1, 1023]
+[cases.large_files]
+defines.SIZE = [32, 8192, 262144, 0, 7, 8193]
+defines.CHUNKSIZE = [31, 16, 33, 1, 1023]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     // write
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "avacado",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
     srand(1);
+    uint8_t buffer[1024];
     for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
         lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
         for (lfs_size_t b = 0; b < chunk; b++) {
@@ -41,7 +47,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // read
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => SIZE;
     srand(1);
@@ -57,15 +63,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # rewriting files
-define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
-define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
-define.CHUNKSIZE = [31, 16, 1]
+[cases.rewriting_files]
+defines.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
+defines.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
+defines.CHUNKSIZE = [31, 16, 1]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     // write
-    lfs_mount(&lfs, &cfg) => 1;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
+    uint8_t buffer[1024];
     lfs_file_open(&lfs, &file, "avacado",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
     srand(1);
@@ -80,7 +89,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // read
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => SIZE1;
     srand(1);
@@ -96,7 +105,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // rewrite
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY) => 0;
     srand(2);
     for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
@@ -110,7 +119,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // read
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => lfs_max(SIZE1, SIZE2);
     srand(2);
@@ -139,15 +148,18 @@ code = '''
     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]
+[cases.appending_files]
+defines.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
+defines.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
+defines.CHUNKSIZE = [31, 16, 1]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     // write
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
+    uint8_t buffer[1024];
     lfs_file_open(&lfs, &file, "avacado",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
     srand(1);
@@ -162,7 +174,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // read
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => SIZE1;
     srand(1);
@@ -178,7 +190,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // append
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_APPEND) => 0;
     srand(2);
     for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
@@ -192,7 +204,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // read
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => SIZE1 + SIZE2;
     srand(1);
@@ -216,15 +228,18 @@ code = '''
     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]
+[cases.truncating_files]
+defines.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
+defines.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
+defines.CHUNKSIZE = [31, 16, 1]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 
     // write
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
+    uint8_t buffer[1024];
     lfs_file_open(&lfs, &file, "avacado",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
     srand(1);
@@ -239,7 +254,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // read
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => SIZE1;
     srand(1);
@@ -255,7 +270,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // truncate
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_TRUNC) => 0;
     srand(2);
     for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
@@ -269,7 +284,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // read
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => SIZE2;
     srand(2);
@@ -285,22 +300,25 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # reentrant file writing
-define.SIZE = [32, 0, 7, 2049]
-define.CHUNKSIZE = [31, 16, 65]
+[cases.reentrant_file_writing]
+defines.SIZE = [32, 0, 7, 2049]
+defines.CHUNKSIZE = [31, 16, 65]
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
+    lfs_file_t file;
+    uint8_t buffer[1024];
     err = lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY);
     assert(err == LFS_ERR_NOENT || err == 0);
     if (err == 0) {
         // can only be 0 (new file) or full size
-        size = lfs_file_size(&lfs, &file);
+        lfs_size_t size = lfs_file_size(&lfs, &file);
         assert(size == 0 || size == SIZE);
         lfs_file_close(&lfs, &file) => 0;
     }
@@ -333,8 +351,8 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # reentrant file writing with syncs
-define = [
+[cases.reentrant_file_writing_sync]
+defines = [
     # append (O(n))
     {MODE='LFS_O_APPEND',   SIZE=[32, 0, 7, 2049],  CHUNKSIZE=[31, 16, 65]},
     # truncate (O(n^2))
@@ -344,17 +362,20 @@ define = [
 ]
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
+    lfs_file_t file;
+    uint8_t buffer[1024];
     err = lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY);
     assert(err == LFS_ERR_NOENT || err == 0);
     if (err == 0) {
         // with syncs we could be any size, but it at least must be valid data
-        size = lfs_file_size(&lfs, &file);
+        lfs_size_t size = lfs_file_size(&lfs, &file);
         assert(size <= SIZE);
         srand(1);
         for (lfs_size_t i = 0; i < size; i += CHUNKSIZE) {
@@ -370,7 +391,7 @@ code = '''
     // write
     lfs_file_open(&lfs, &file, "avacado",
         LFS_O_WRONLY | LFS_O_CREAT | MODE) => 0;
-    size = lfs_file_size(&lfs, &file);
+    lfs_size_t size = lfs_file_size(&lfs, &file);
     assert(size <= SIZE);
     srand(1);
     lfs_size_t skip = (MODE == LFS_O_APPEND) ? size : 0;
@@ -403,19 +424,22 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # many files
-define.N = 300
+[cases.many_files]
+defines.N = 300
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
     // create N files of 7 bytes
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        lfs_file_t file;
+        char path[1024];
         sprintf(path, "file_%03d", i);
         lfs_file_open(&lfs, &file, path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         char wbuffer[1024];
-        size = 7;
-        snprintf(wbuffer, size, "Hi %03d", i);
+        lfs_size_t size = 7;
+        sprintf(wbuffer, "Hi %03d", i);
         lfs_file_write(&lfs, &file, wbuffer, size) => size;
         lfs_file_close(&lfs, &file) => 0;
 
@@ -428,25 +452,28 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # many files with power cycle
-define.N = 300
+[cases.many_files_power_cycle]
+defines.N = 300
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
     // create N files of 7 bytes
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        lfs_file_t file;
+        char path[1024];
         sprintf(path, "file_%03d", i);
         lfs_file_open(&lfs, &file, path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         char wbuffer[1024];
-        size = 7;
-        snprintf(wbuffer, size, "Hi %03d", i);
+        lfs_size_t size = 7;
+        sprintf(wbuffer, "Hi %03d", i);
         lfs_file_write(&lfs, &file, wbuffer, size) => size;
         lfs_file_close(&lfs, &file) => 0;
         lfs_unmount(&lfs) => 0;
 
         char rbuffer[1024];
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
         lfs_file_read(&lfs, &file, rbuffer, size) => size;
         assert(strcmp(rbuffer, wbuffer) == 0);
@@ -455,22 +482,25 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # many files with power loss
-define.N = 300
+[cases.many_files_power_loss]
+defines.N = 300
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
     // create N files of 7 bytes
     for (int i = 0; i < N; i++) {
+        lfs_file_t file;
+        char path[1024];
         sprintf(path, "file_%03d", i);
         err = lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT);
         char wbuffer[1024];
-        size = 7;
-        snprintf(wbuffer, size, "Hi %03d", i);
+        lfs_size_t size = 7;
+        sprintf(wbuffer, "Hi %03d", i);
         if ((lfs_size_t)lfs_file_size(&lfs, &file) != size) {
             lfs_file_write(&lfs, &file, wbuffer, size) => size;
         }

+ 49 - 23
tests/test_interspersed.toml

@@ -1,13 +1,15 @@
 
-[[case]] # interspersed file test
-define.SIZE = [10, 100]
-define.FILES = [4, 10, 26] 
+[cases.interspersed_files]
+defines.SIZE = [10, 100]
+defines.FILES = [4, 10, 26] 
 code = '''
+    lfs_t lfs;
     lfs_file_t files[FILES];
     const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int j = 0; j < FILES; j++) {
+        char path[1024];
         sprintf(path, "%c", alphas[j]);
         lfs_file_open(&lfs, &files[j], path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
@@ -23,7 +25,9 @@ code = '''
         lfs_file_close(&lfs, &files[j]);
     }
 
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(strcmp(info.name, ".") == 0);
     assert(info.type == LFS_TYPE_DIR);
@@ -31,6 +35,7 @@ code = '''
     assert(strcmp(info.name, "..") == 0);
     assert(info.type == LFS_TYPE_DIR);
     for (int j = 0; j < FILES; j++) {
+        char path[1024];
         sprintf(path, "%c", alphas[j]);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(strcmp(info.name, path) == 0);
@@ -41,12 +46,14 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
 
     for (int j = 0; j < FILES; j++) {
+        char path[1024];
         sprintf(path, "%c", alphas[j]);
         lfs_file_open(&lfs, &files[j], path, LFS_O_RDONLY) => 0;
     }
 
     for (int i = 0; i < 10; i++) {
         for (int j = 0; j < FILES; j++) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &files[j], buffer, 1) => 1;
             assert(buffer[0] == alphas[j]);
         }
@@ -59,15 +66,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # interspersed remove file test
-define.SIZE = [10, 100]
-define.FILES = [4, 10, 26]
+[cases.interspersed_remove_files]
+defines.SIZE = [10, 100]
+defines.FILES = [4, 10, 26]
 code = '''
+    lfs_t lfs;
     const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int j = 0; j < FILES; j++) {
+        char path[1024];
         sprintf(path, "%c", alphas[j]);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         for (int i = 0; i < SIZE; i++) {
@@ -77,18 +87,22 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "zzz", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     for (int j = 0; j < FILES; j++) {
         lfs_file_write(&lfs, &file, (const void*)"~", 1) => 1;
         lfs_file_sync(&lfs, &file) => 0;
 
+        char path[1024];
         sprintf(path, "%c", alphas[j]);
         lfs_remove(&lfs, path) => 0;
     }
     lfs_file_close(&lfs, &file);
 
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(strcmp(info.name, ".") == 0);
     assert(info.type == LFS_TYPE_DIR);
@@ -104,6 +118,7 @@ code = '''
 
     lfs_file_open(&lfs, &file, "zzz", LFS_O_RDONLY) => 0;
     for (int i = 0; i < FILES; i++) {
+        uint8_t buffer[1024];
         lfs_file_read(&lfs, &file, buffer, 1) => 1;
         assert(buffer[0] == '~');
     }
@@ -112,11 +127,12 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # remove inconveniently test
-define.SIZE = [10, 100]
+[cases.remove_inconveniently]
+defines.SIZE = [10, 100]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_t files[3];
     lfs_file_open(&lfs, &files[0], "e", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     lfs_file_open(&lfs, &files[1], "f", LFS_O_WRONLY | LFS_O_CREAT) => 0;
@@ -140,7 +156,9 @@ code = '''
     lfs_file_close(&lfs, &files[1]);
     lfs_file_close(&lfs, &files[2]);
 
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(strcmp(info.name, ".") == 0);
     assert(info.type == LFS_TYPE_DIR);
@@ -161,6 +179,7 @@ code = '''
     lfs_file_open(&lfs, &files[0], "e", LFS_O_RDONLY) => 0;
     lfs_file_open(&lfs, &files[1], "g", LFS_O_RDONLY) => 0;
     for (int i = 0; i < SIZE; i++) {
+        uint8_t buffer[1024];
         lfs_file_read(&lfs, &files[0], buffer, 1) => 1;
         assert(buffer[0] == 'e');
         lfs_file_read(&lfs, &files[1], buffer, 1) => 1;
@@ -172,21 +191,23 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # reentrant interspersed file test
-define.SIZE = [10, 100]
-define.FILES = [4, 10, 26] 
+[cases.reentrant_interspersed_files]
+defines.SIZE = [10, 100]
+defines.FILES = [4, 10, 26] 
 reentrant = true
 code = '''
+    lfs_t lfs;
     lfs_file_t files[FILES];
     const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
 
-    err = lfs_mount(&lfs, &cfg);
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
     for (int j = 0; j < FILES; j++) {
+        char path[1024];
         sprintf(path, "%c", alphas[j]);
         lfs_file_open(&lfs, &files[j], path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
@@ -194,8 +215,8 @@ code = '''
 
     for (int i = 0; i < SIZE; i++) {
         for (int j = 0; j < FILES; j++) {
-            size = lfs_file_size(&lfs, &files[j]);
-            assert((int)size >= 0);
+            lfs_ssize_t size = lfs_file_size(&lfs, &files[j]);
+            assert(size >= 0);
             if ((int)size <= i) {
                 lfs_file_write(&lfs, &files[j], &alphas[j], 1) => 1;
                 lfs_file_sync(&lfs, &files[j]) => 0;
@@ -207,7 +228,9 @@ code = '''
         lfs_file_close(&lfs, &files[j]);
     }
 
+    lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
+    struct lfs_info info;
     lfs_dir_read(&lfs, &dir, &info) => 1;
     assert(strcmp(info.name, ".") == 0);
     assert(info.type == LFS_TYPE_DIR);
@@ -215,6 +238,7 @@ code = '''
     assert(strcmp(info.name, "..") == 0);
     assert(info.type == LFS_TYPE_DIR);
     for (int j = 0; j < FILES; j++) {
+        char path[1024];
         sprintf(path, "%c", alphas[j]);
         lfs_dir_read(&lfs, &dir, &info) => 1;
         assert(strcmp(info.name, path) == 0);
@@ -225,12 +249,14 @@ code = '''
     lfs_dir_close(&lfs, &dir) => 0;
 
     for (int j = 0; j < FILES; j++) {
+        char path[1024];
         sprintf(path, "%c", alphas[j]);
         lfs_file_open(&lfs, &files[j], path, LFS_O_RDONLY) => 0;
     }
 
     for (int i = 0; i < 10; i++) {
         for (int j = 0; j < FILES; j++) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &files[j], buffer, 1) => 1;
             assert(buffer[0] == alphas[j]);
         }

文件差异内容过多而无法显示
+ 269 - 188
tests/test_move.toml


+ 35 - 26
tests/test_orphans.toml

@@ -1,9 +1,10 @@
-[[case]] # orphan test
+[cases.orphan]
 in = "lfs.c"
-if = 'LFS_PROG_SIZE <= 0x3fe' # only works with one crc per commit
+if = 'PROG_SIZE <= 0x3fe' # only works with one crc per commit
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    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;
@@ -13,29 +14,31 @@ code = '''
     // 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_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
     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) {
+    uint8_t buffer[BLOCK_SIZE];
+    cfg->read(cfg, block, 0, buffer, BLOCK_SIZE) => 0;
+    int off = BLOCK_SIZE-1;
+    while (off >= 0 && buffer[off] == 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;
+    memset(&buffer[off-3], BLOCK_SIZE, 3);
+    cfg->erase(cfg, block) => 0;
+    cfg->prog(cfg, block, 0, buffer, BLOCK_SIZE) => 0;
+    cfg->sync(cfg) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     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_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;
@@ -48,7 +51,7 @@ code = '''
     lfs_fs_size(&lfs) => 8;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 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;
@@ -56,43 +59,48 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # reentrant testing for orphans, basically just spam mkdir/remove
+# reentrant testing for orphans, basically just spam mkdir/remove
+[cases.reentrant_orphan]
 reentrant = true
 # TODO fix this case, caused by non-DAG trees
-if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
-define = [
+if = '!(DEPTH == 3 && CACHE_SIZE != 64)'
+defines = [
     {FILES=6,  DEPTH=1, CYCLES=20},
     {FILES=26, DEPTH=1, CYCLES=20},
     {FILES=3,  DEPTH=3, CYCLES=20},
 ]
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
     srand(1);
     const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
-    for (int i = 0; i < CYCLES; i++) {
+    for (unsigned i = 0; i < CYCLES; i++) {
         // create random path
         char full_path[256];
-        for (int d = 0; d < DEPTH; d++) {
+        for (unsigned d = 0; d < DEPTH; d++) {
             sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
         }
 
         // if it does not exist, we create it, else we destroy
+        struct lfs_info info;
         int res = lfs_stat(&lfs, full_path, &info);
         if (res == LFS_ERR_NOENT) {
             // create each directory in turn, ignore if dir already exists
-            for (int d = 0; d < DEPTH; d++) {
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
                 strcpy(path, full_path);
                 path[2*d+2] = '\0';
                 err = lfs_mkdir(&lfs, path);
                 assert(!err || err == LFS_ERR_EXIST);
             }
 
-            for (int d = 0; d < DEPTH; d++) {
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
                 strcpy(path, full_path);
                 path[2*d+2] = '\0';
                 lfs_stat(&lfs, path, &info) => 0;
@@ -106,6 +114,7 @@ code = '''
 
             // try to delete path in reverse order, ignore if dir is not empty
             for (int d = DEPTH-1; d >= 0; d--) {
+                char path[1024];
                 strcpy(path, full_path);
                 path[2*d+2] = '\0';
                 err = lfs_remove(&lfs, path);

+ 82 - 39
tests/test_paths.toml

@@ -1,13 +1,16 @@
 
-[[case]] # simple path test
+# simple path test
+[cases.path]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "tea") => 0;
     lfs_mkdir(&lfs, "tea/hottea") => 0;
     lfs_mkdir(&lfs, "tea/warmtea") => 0;
     lfs_mkdir(&lfs, "tea/coldtea") => 0;
 
+    struct lfs_info info;
     lfs_stat(&lfs, "tea/hottea", &info) => 0;
     assert(strcmp(info.name, "hottea") == 0);
     lfs_stat(&lfs, "/tea/hottea", &info) => 0;
@@ -21,15 +24,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # redundant slashes
+# redundant slashes
+[cases.redundant_slashes]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "tea") => 0;
     lfs_mkdir(&lfs, "tea/hottea") => 0;
     lfs_mkdir(&lfs, "tea/warmtea") => 0;
     lfs_mkdir(&lfs, "tea/coldtea") => 0;
 
+    struct lfs_info info;
     lfs_stat(&lfs, "/tea/hottea", &info) => 0;
     assert(strcmp(info.name, "hottea") == 0);
     lfs_stat(&lfs, "//tea//hottea", &info) => 0;
@@ -45,15 +51,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # dot path test
+# dot path test
+[cases.dot_path]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "tea") => 0;
     lfs_mkdir(&lfs, "tea/hottea") => 0;
     lfs_mkdir(&lfs, "tea/warmtea") => 0;
     lfs_mkdir(&lfs, "tea/coldtea") => 0;
 
+    struct lfs_info info;
     lfs_stat(&lfs, "./tea/hottea", &info) => 0;
     assert(strcmp(info.name, "hottea") == 0);
     lfs_stat(&lfs, "/./tea/hottea", &info) => 0;
@@ -71,10 +80,12 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # dot dot path test
+# dot dot path test
+[cases.dot_dot_path]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "tea") => 0;
     lfs_mkdir(&lfs, "tea/hottea") => 0;
     lfs_mkdir(&lfs, "tea/warmtea") => 0;
@@ -84,6 +95,7 @@ code = '''
     lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
     lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
 
+    struct lfs_info info;
     lfs_stat(&lfs, "coffee/../tea/hottea", &info) => 0;
     assert(strcmp(info.name, "hottea") == 0);
     lfs_stat(&lfs, "tea/coldtea/../hottea", &info) => 0;
@@ -101,15 +113,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # trailing dot path test
+# trailing dot path test
+[cases.trailing_dot_path]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "tea") => 0;
     lfs_mkdir(&lfs, "tea/hottea") => 0;
     lfs_mkdir(&lfs, "tea/warmtea") => 0;
     lfs_mkdir(&lfs, "tea/coldtea") => 0;
 
+    struct lfs_info info;
     lfs_stat(&lfs, "tea/hottea/", &info) => 0;
     assert(strcmp(info.name, "hottea") == 0);
     lfs_stat(&lfs, "tea/hottea/.", &info) => 0;
@@ -123,11 +138,14 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # leading dot path test
+# leading dot path test
+[cases.leading_dot_path]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, ".milk") => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, ".milk", &info) => 0;
     strcmp(info.name, ".milk") => 0;
     lfs_stat(&lfs, "tea/.././.milk", &info) => 0;
@@ -135,10 +153,12 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # root dot dot path test
+# root dot dot path test
+[cases.root_dot_dot_path]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "tea") => 0;
     lfs_mkdir(&lfs, "tea/hottea") => 0;
     lfs_mkdir(&lfs, "tea/warmtea") => 0;
@@ -148,6 +168,7 @@ code = '''
     lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
     lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
 
+    struct lfs_info info;
     lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0;
     strcmp(info.name, "hottea") => 0;
 
@@ -159,10 +180,13 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # invalid path tests
+# invalid path tests
+[cases.invalid_path]
 code = '''
-    lfs_format(&lfs, &cfg);
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg);
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "dirt", &info) => LFS_ERR_NOENT;
     lfs_stat(&lfs, "dirt/ground", &info) => LFS_ERR_NOENT;
     lfs_stat(&lfs, "dirt/ground/earth", &info) => LFS_ERR_NOENT;
@@ -172,6 +196,7 @@ code = '''
     lfs_remove(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT;
 
     lfs_mkdir(&lfs, "dirt/ground") => LFS_ERR_NOENT;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "dirt/ground", LFS_O_WRONLY | LFS_O_CREAT)
             => LFS_ERR_NOENT;
     lfs_mkdir(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT;
@@ -180,15 +205,19 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # root operations
+# root operations
+[cases.root]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "/", &info) => 0;
     assert(strcmp(info.name, "/") == 0);
     assert(info.type == LFS_TYPE_DIR);
 
     lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "/", LFS_O_WRONLY | LFS_O_CREAT)
             => LFS_ERR_ISDIR;
 
@@ -196,10 +225,13 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # root representations
+# root representations
+[cases.root_reprs]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "/", &info) => 0;
     assert(strcmp(info.name, "/") == 0);
     assert(info.type == LFS_TYPE_DIR);
@@ -221,10 +253,13 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # superblock conflict test
+# superblock conflict test
+[cases.superblock_conflict]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT;
     lfs_remove(&lfs, "littlefs") => LFS_ERR_NOENT;
 
@@ -237,18 +272,22 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # max path test
+# max path test
+[cases.max_path]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "coffee") => 0;
     lfs_mkdir(&lfs, "coffee/hotcoffee") => 0;
     lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
     lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
 
+    char path[1024];
     memset(path, 'w', LFS_NAME_MAX+1);
     path[LFS_NAME_MAX+1] = '\0';
     lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT)
             => LFS_ERR_NAMETOOLONG;
 
@@ -261,19 +300,23 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # really big path test
+# really big path test
+[cases.really_big_path]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_mkdir(&lfs, "coffee") => 0;
     lfs_mkdir(&lfs, "coffee/hotcoffee") => 0;
     lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
     lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
 
+    char path[1024];
     memset(path, 'w', LFS_NAME_MAX);
     path[LFS_NAME_MAX] = '\0';
     lfs_mkdir(&lfs, path) => 0;
     lfs_remove(&lfs, path) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, path,
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
     lfs_file_close(&lfs, &file) => 0;

+ 99 - 63
tests/test_relocations.toml

@@ -1,15 +1,18 @@
 # specific corner cases worth explicitly testing for
-[[case]] # dangling split dir test
-define.ITERATIONS = 20
-define.COUNT = 10
-define.LFS_BLOCK_CYCLES = [8, 1]
+[cases.dangling_split_dir]
+defines.ITERATIONS = 20
+defines.COUNT = 10
+defines.BLOCK_CYCLES = [8, 1]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
     // fill up filesystem so only ~16 blocks are left
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0;
+    uint8_t buffer[512];
     memset(buffer, 0, 512);
-    while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) {
+    while (BLOCK_COUNT - lfs_fs_size(&lfs) > 16) {
         lfs_file_write(&lfs, &file, buffer, 512) => 512;
     }
     lfs_file_close(&lfs, &file) => 0;
@@ -17,18 +20,22 @@ code = '''
     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++) {
+    lfs_mount(&lfs, cfg) => 0;
+    for (unsigned j = 0; j < ITERATIONS; j++) {
+        for (unsigned i = 0; i < COUNT; i++) {
+            char path[1024];
             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_t dir;
+        struct lfs_info info;
         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++) {
+        for (unsigned i = 0; i < COUNT; i++) {
+            char path[1024];
             sprintf(path, "test%03d_loooooooooooooooooong_name", i);
             lfs_dir_read(&lfs, &dir, &info) => 1;
             strcmp(info.name, path) => 0;
@@ -36,46 +43,54 @@ code = '''
         lfs_dir_read(&lfs, &dir, &info) => 0;
         lfs_dir_close(&lfs, &dir) => 0;
 
-        if (j == ITERATIONS-1) {
+        if (j == (unsigned)ITERATIONS-1) {
             break;
         }
 
-        for (int i = 0; i < COUNT; i++) {
+        for (unsigned i = 0; i < COUNT; i++) {
+            char path[1024];
             sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
             lfs_remove(&lfs, path) => 0;
         }
     }
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_dir_t dir;
+    struct lfs_info info;
     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++) {
+    for (unsigned i = 0; i < COUNT; i++) {
+        char path[1024];
         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++) {
+    for (unsigned i = 0; i < COUNT; i++) {
+        char path[1024];
         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
-define.LFS_BLOCK_CYCLES = [8, 1]
+[cases.outdated_head]
+defines.ITERATIONS = 20
+defines.COUNT = 10
+defines.BLOCK_CYCLES = [8, 1]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
     // fill up filesystem so only ~16 blocks are left
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0;
+    uint8_t buffer[512];
     memset(buffer, 0, 512);
-    while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) {
+    while (BLOCK_COUNT - lfs_fs_size(&lfs) > 16) {
         lfs_file_write(&lfs, &file, buffer, 512) => 512;
     }
     lfs_file_close(&lfs, &file) => 0;
@@ -83,18 +98,22 @@ code = '''
     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++) {
+    lfs_mount(&lfs, cfg) => 0;
+    for (unsigned j = 0; j < ITERATIONS; j++) {
+        for (unsigned i = 0; i < COUNT; i++) {
+            char path[1024];
             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_t dir;
+        struct lfs_info info;
         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++) {
+        for (unsigned i = 0; i < COUNT; i++) {
+            char path[1024];
             sprintf(path, "test%03d_loooooooooooooooooong_name", i);
             lfs_dir_read(&lfs, &dir, &info) => 1;
             strcmp(info.name, path) => 0;
@@ -110,7 +129,8 @@ code = '''
         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++) {
+        for (unsigned i = 0; i < COUNT; i++) {
+            char path[1024];
             sprintf(path, "test%03d_loooooooooooooooooong_name", i);
             lfs_dir_read(&lfs, &dir, &info) => 1;
             strcmp(info.name, path) => 0;
@@ -126,7 +146,8 @@ code = '''
         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++) {
+        for (unsigned i = 0; i < COUNT; i++) {
+            char path[1024];
             sprintf(path, "test%03d_loooooooooooooooooong_name", i);
             lfs_dir_read(&lfs, &dir, &info) => 1;
             strcmp(info.name, path) => 0;
@@ -135,7 +156,8 @@ code = '''
         lfs_dir_read(&lfs, &dir, &info) => 0;
         lfs_dir_close(&lfs, &dir) => 0;
 
-        for (int i = 0; i < COUNT; i++) {
+        for (unsigned i = 0; i < COUNT; i++) {
+            char path[1024];
             sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
             lfs_remove(&lfs, path) => 0;
         }
@@ -143,45 +165,50 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # reentrant testing for relocations, this is the same as the
-         # orphan testing, except here we also set block_cycles so that
-         # almost every tree operation needs a relocation
+# reentrant testing for relocations, this is the same as the
+# orphan testing, except here we also set block_cycles so that
+# almost every tree operation needs a relocation
+[cases.reentrant_relocations]
 reentrant = true
 # TODO fix this case, caused by non-DAG trees
-if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
-define = [
-    {FILES=6,  DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
-    {FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
-    {FILES=3,  DEPTH=3, CYCLES=20, LFS_BLOCK_CYCLES=1},
+if = '!(DEPTH == 3 && CACHE_SIZE != 64)'
+defines = [
+    {FILES=6,  DEPTH=1, CYCLES=20, BLOCK_CYCLES=1},
+    {FILES=26, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1},
+    {FILES=3,  DEPTH=3, CYCLES=20, BLOCK_CYCLES=1},
 ]
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
     srand(1);
     const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
-    for (int i = 0; i < CYCLES; i++) {
+    for (unsigned i = 0; i < CYCLES; i++) {
         // create random path
         char full_path[256];
-        for (int d = 0; d < DEPTH; d++) {
+        for (unsigned d = 0; d < DEPTH; d++) {
             sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
         }
 
         // if it does not exist, we create it, else we destroy
+        struct lfs_info info;
         int res = lfs_stat(&lfs, full_path, &info);
         if (res == LFS_ERR_NOENT) {
             // create each directory in turn, ignore if dir already exists
-            for (int d = 0; d < DEPTH; d++) {
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
                 strcpy(path, full_path);
                 path[2*d+2] = '\0';
                 err = lfs_mkdir(&lfs, path);
                 assert(!err || err == LFS_ERR_EXIST);
             }
 
-            for (int d = 0; d < DEPTH; d++) {
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
                 strcpy(path, full_path);
                 path[2*d+2] = '\0';
                 lfs_stat(&lfs, path, &info) => 0;
@@ -194,7 +221,8 @@ code = '''
             assert(info.type == LFS_TYPE_DIR);
 
             // try to delete path in reverse order, ignore if dir is not empty
-            for (int d = DEPTH-1; d >= 0; d--) {
+            for (unsigned d = DEPTH-1; d+1 > 0; d--) {
+                char path[1024];
                 strcpy(path, full_path);
                 path[2*d+2] = '\0';
                 err = lfs_remove(&lfs, path);
@@ -207,44 +235,49 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # reentrant testing for relocations, but now with random renames!
+# reentrant testing for relocations, but now with random renames!
+[cases.reentrant_relocations_renames]
 reentrant = true
 # TODO fix this case, caused by non-DAG trees
-if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
-define = [
-    {FILES=6,  DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
-    {FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
-    {FILES=3,  DEPTH=3, CYCLES=20, LFS_BLOCK_CYCLES=1},
+if = '!(DEPTH == 3 && CACHE_SIZE != 64)'
+defines = [
+    {FILES=6,  DEPTH=1, CYCLES=20, BLOCK_CYCLES=1},
+    {FILES=26, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1},
+    {FILES=3,  DEPTH=3, CYCLES=20, BLOCK_CYCLES=1},
 ]
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
     srand(1);
     const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
-    for (int i = 0; i < CYCLES; i++) {
+    for (unsigned i = 0; i < CYCLES; i++) {
         // create random path
         char full_path[256];
-        for (int d = 0; d < DEPTH; d++) {
+        for (unsigned d = 0; d < DEPTH; d++) {
             sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
         }
 
         // if it does not exist, we create it, else we destroy
+        struct lfs_info info;
         int res = lfs_stat(&lfs, full_path, &info);
         assert(!res || res == LFS_ERR_NOENT);
         if (res == LFS_ERR_NOENT) {
             // create each directory in turn, ignore if dir already exists
-            for (int d = 0; d < DEPTH; d++) {
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
                 strcpy(path, full_path);
                 path[2*d+2] = '\0';
                 err = lfs_mkdir(&lfs, path);
                 assert(!err || err == LFS_ERR_EXIST);
             }
 
-            for (int d = 0; d < DEPTH; d++) {
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
                 strcpy(path, full_path);
                 path[2*d+2] = '\0';
                 lfs_stat(&lfs, path, &info) => 0;
@@ -257,7 +290,7 @@ code = '''
 
             // create new random path
             char new_path[256];
-            for (int d = 0; d < DEPTH; d++) {
+            for (unsigned d = 0; d < DEPTH; d++) {
                 sprintf(&new_path[2*d], "/%c", alpha[rand() % FILES]);
             }
 
@@ -266,7 +299,8 @@ code = '''
             assert(!res || res == LFS_ERR_NOENT);
             if (res == LFS_ERR_NOENT) {
                 // stop once some dir is renamed
-                for (int d = 0; d < DEPTH; d++) {
+                for (unsigned d = 0; d < DEPTH; d++) {
+                    char path[1024];
                     strcpy(&path[2*d], &full_path[2*d]);
                     path[2*d+2] = '\0';
                     strcpy(&path[128+2*d], &new_path[2*d]);
@@ -278,7 +312,8 @@ code = '''
                     }
                 }
 
-                for (int d = 0; d < DEPTH; d++) {
+                for (unsigned d = 0; d < DEPTH; d++) {
+                    char path[1024];
                     strcpy(path, new_path);
                     path[2*d+2] = '\0';
                     lfs_stat(&lfs, path, &info) => 0;
@@ -290,7 +325,8 @@ code = '''
             } else {
                 // try to delete path in reverse order,
                 // ignore if dir is not empty
-                for (int d = DEPTH-1; d >= 0; d--) {
+                for (unsigned d = DEPTH-1; d+1 > 0; d--) {
+                    char path[1024];
                     strcpy(path, full_path);
                     path[2*d+2] = '\0';
                     err = lfs_remove(&lfs, path);

+ 60 - 37
tests/test_seek.toml

@@ -1,6 +1,7 @@
 
-[[case]] # simple file seek
-define = [
+# simple file seek
+[cases.seek]
+defines = [
     {COUNT=132, SKIP=4},
     {COUNT=132, SKIP=128},
     {COUNT=200, SKIP=10},
@@ -9,11 +10,14 @@ define = [
     {COUNT=4,   SKIP=2},
 ]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "kitty",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
-    size = strlen("kittycatcat");
+    size_t size = strlen("kittycatcat");
+    uint8_t buffer[1024];
     memcpy(buffer, "kittycatcat", size);
     for (int j = 0; j < COUNT; j++) {
         lfs_file_write(&lfs, &file, buffer, size);
@@ -21,7 +25,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "kitty", LFS_O_RDONLY) => 0;
 
     lfs_soff_t pos = -1;
@@ -68,8 +72,9 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # simple file seek and write
-define = [
+# simple file seek and write
+[cases.seek_write]
+defines = [
     {COUNT=132, SKIP=4},
     {COUNT=132, SKIP=128},
     {COUNT=200, SKIP=10},
@@ -78,11 +83,14 @@ define = [
     {COUNT=4,   SKIP=2},
 ]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "kitty",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
-    size = strlen("kittycatcat");
+    size_t size = strlen("kittycatcat");
+    uint8_t buffer[1024];
     memcpy(buffer, "kittycatcat", size);
     for (int j = 0; j < COUNT; j++) {
         lfs_file_write(&lfs, &file, buffer, size);
@@ -90,7 +98,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0;
 
     lfs_soff_t pos = -1;
@@ -129,15 +137,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # boundary seek and writes
-define.COUNT = 132
-define.OFFSETS = '"{512, 1020, 513, 1021, 511, 1019, 1441}"'
+# boundary seek and writes
+[cases.boundary_seek_write]
+defines.COUNT = 132
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "kitty",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
-    size = strlen("kittycatcat");
+    size_t size = strlen("kittycatcat");
+    uint8_t buffer[1024];
     memcpy(buffer, "kittycatcat", size);
     for (int j = 0; j < COUNT; j++) {
         lfs_file_write(&lfs, &file, buffer, size);
@@ -145,11 +156,11 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0;
 
     size = strlen("hedgehoghog");
-    const lfs_soff_t offsets[] = OFFSETS;
+    const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019, 1441};
 
     for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
         lfs_soff_t off = offsets[i];
@@ -183,8 +194,9 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # out of bounds seek
-define = [
+# out of bounds seek
+[cases.out_of_bounds_seek]
+defines = [
     {COUNT=132, SKIP=4},
     {COUNT=132, SKIP=128},
     {COUNT=200, SKIP=10},
@@ -193,18 +205,21 @@ define = [
     {COUNT=4,   SKIP=3},
 ]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "kitty",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
-    size = strlen("kittycatcat");
+    size_t size = strlen("kittycatcat");
+    uint8_t buffer[1024];
     memcpy(buffer, "kittycatcat", size);
     for (int j = 0; j < COUNT; j++) {
         lfs_file_write(&lfs, &file, buffer, size);
     }
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0;
 
     size = strlen("kittycatcat");
@@ -238,16 +253,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # inline write and seek
-define.SIZE = [2, 4, 128, 132]
+# inline write and seek
+[cases.inline_write_seek]
+defines.SIZE = [2, 4, 128, 132]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "tinykitty",
             LFS_O_RDWR | LFS_O_CREAT) => 0;
     int j = 0;
     int k = 0;
 
+    uint8_t buffer[1024];
     memcpy(buffer, "abcdefghijklmnopqrstuvwxyz", 26);
     for (unsigned i = 0; i < SIZE; i++) {
         lfs_file_write(&lfs, &file, &buffer[j++ % 26], 1) => 1;
@@ -305,16 +324,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # file seek and write with power-loss
+# file seek and write with power-loss
+[cases.reentrant_seek_write]
 # must be power-of-2 for quadratic probing to be exhaustive
-define.COUNT = [4, 64, 128]
+defines.COUNT = [4, 64, 128]
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
+    lfs_file_t file;
+    uint8_t buffer[1024];
     err = lfs_file_open(&lfs, &file, "kitty", LFS_O_RDONLY);
     assert(!err || err == LFS_ERR_NOENT);
     if (!err) {
@@ -334,14 +357,14 @@ code = '''
     if (lfs_file_size(&lfs, &file) == 0) {
         for (int j = 0; j < COUNT; j++) {
             strcpy((char*)buffer, "kittycatcat");
-            size = strlen((char*)buffer);
+            size_t size = strlen((char*)buffer);
             lfs_file_write(&lfs, &file, buffer, size) => size;
         }
     }
     lfs_file_close(&lfs, &file) => 0;
 
     strcpy((char*)buffer, "doggodogdog");
-    size = strlen((char*)buffer);
+    size_t size = strlen((char*)buffer);
 
     lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0;
     lfs_file_size(&lfs, &file) => COUNT*size;

+ 55 - 31
tests/test_superblocks.toml

@@ -1,41 +1,53 @@
-[[case]] # simple formatting test
+# simple formatting test
+[cases.format]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
 '''
 
-[[case]] # mount/unmount
+# mount/unmount
+[cases.mount]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # reentrant format
+# reentrant format
+[cases.reentrant_format]
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # invalid mount
+# invalid mount
+[cases.invalid_mount]
 code = '''
-    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
+    lfs_t lfs;
+    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
 '''
 
-[[case]] # expanding superblock
-define.LFS_BLOCK_CYCLES = [32, 33, 1]
-define.N = [10, 100, 1000]
+# expanding superblock
+[cases.expanding_superblock]
+defines.LFS_BLOCK_CYCLES = [32, 33, 1]
+defines.N = [10, 100, 1000]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, "dummy",
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         lfs_file_close(&lfs, &file) => 0;
+        struct lfs_info info;
         lfs_stat(&lfs, "dummy", &info) => 0;
         assert(strcmp(info.name, "dummy") == 0);
         assert(info.type == LFS_TYPE_REG);
@@ -44,25 +56,30 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // one last check after power-cycle
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "dummy",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
     lfs_file_close(&lfs, &file) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "dummy", &info) => 0;
     assert(strcmp(info.name, "dummy") == 0);
     assert(info.type == LFS_TYPE_REG);
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # expanding superblock with power cycle
-define.LFS_BLOCK_CYCLES = [32, 33, 1]
-define.N = [10, 100, 1000]
+# expanding superblock with power cycle
+[cases.expanding_superblock_power_cycle]
+defines.LFS_BLOCK_CYCLES = [32, 33, 1]
+defines.N = [10, 100, 1000]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
     for (int i = 0; i < N; i++) {
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
         // remove lingering dummy?
-        err = lfs_stat(&lfs, "dummy", &info);
+        struct lfs_info info;
+        int err = lfs_stat(&lfs, "dummy", &info);
         assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
         if (!err) {
             assert(strcmp(info.name, "dummy") == 0);
@@ -70,6 +87,7 @@ code = '''
             lfs_remove(&lfs, "dummy") => 0;
         }
 
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, "dummy",
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         lfs_file_close(&lfs, &file) => 0;
@@ -80,26 +98,30 @@ code = '''
     }
 
     // one last check after power-cycle
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "dummy", &info) => 0;
     assert(strcmp(info.name, "dummy") == 0);
     assert(info.type == LFS_TYPE_REG);
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # reentrant expanding superblock
-define.LFS_BLOCK_CYCLES = [2, 1]
-define.N = 24
+# reentrant expanding superblock
+[cases.reentrant_expanding_superblock]
+defines.LFS_BLOCK_CYCLES = [2, 1]
+defines.N = 24
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
 
     for (int i = 0; i < N; i++) {
         // remove lingering dummy?
+        struct lfs_info info;
         err = lfs_stat(&lfs, "dummy", &info);
         assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
         if (!err) {
@@ -108,6 +130,7 @@ code = '''
             lfs_remove(&lfs, "dummy") => 0;
         }
 
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, "dummy",
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
         lfs_file_close(&lfs, &file) => 0;
@@ -119,7 +142,8 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // one last check after power-cycle
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
     lfs_stat(&lfs, "dummy", &info) => 0;
     assert(strcmp(info.name, "dummy") == 0);
     assert(info.type == LFS_TYPE_REG);

+ 96 - 58
tests/test_truncate.toml

@@ -1,14 +1,18 @@
-[[case]] # simple truncate
-define.MEDIUMSIZE = [32, 2048]
-define.LARGESIZE = 8192
+# simple truncate
+[cases.truncate]
+defines.MEDIUMSIZE = [32, 2048]
+defines.LARGESIZE = 8192
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "baldynoop",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 
+    uint8_t buffer[1024];
     strcpy((char*)buffer, "hair");
-    size = strlen((char*)buffer);
+    size_t size = strlen((char*)buffer);
     for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -17,7 +21,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
     
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDWR) => 0;
     lfs_file_size(&lfs, &file) => LARGESIZE;
 
@@ -27,7 +31,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => MEDIUMSIZE;
 
@@ -42,17 +46,21 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # truncate and read
-define.MEDIUMSIZE = [32, 2048]
-define.LARGESIZE = 8192
+# truncate and read
+[cases.truncate_read]
+defines.MEDIUMSIZE = [32, 2048]
+defines.LARGESIZE = 8192
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "baldyread",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 
+    uint8_t buffer[1024];
     strcpy((char*)buffer, "hair");
-    size = strlen((char*)buffer);
+    size_t size = strlen((char*)buffer);
     for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -61,7 +69,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "baldyread", LFS_O_RDWR) => 0;
     lfs_file_size(&lfs, &file) => LARGESIZE;
 
@@ -78,7 +86,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "baldyread", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => MEDIUMSIZE;
 
@@ -93,14 +101,18 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # write, truncate, and read
+# write, truncate, and read
+[cases.write_truncate_read]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "sequence",
             LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 
-    size = lfs_min(lfs.cfg->cache_size, sizeof(buffer)/2);
+    uint8_t buffer[1024];
+    size_t size = lfs_min(lfs.cfg->cache_size, sizeof(buffer)/2);
     lfs_size_t qsize = size / 4;
     uint8_t *wb = buffer;
     uint8_t *rb = buffer + size;
@@ -145,17 +157,21 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # truncate and write
-define.MEDIUMSIZE = [32, 2048]
-define.LARGESIZE = 8192
+# truncate and write
+[cases.truncate_write]
+defines.MEDIUMSIZE = [32, 2048]
+defines.LARGESIZE = 8192
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "baldywrite",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 
+    uint8_t buffer[1024];
     strcpy((char*)buffer, "hair");
-    size = strlen((char*)buffer);
+    size_t size = strlen((char*)buffer);
     for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -164,7 +180,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDWR) => 0;
     lfs_file_size(&lfs, &file) => LARGESIZE;
 
@@ -181,7 +197,7 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDONLY) => 0;
     lfs_file_size(&lfs, &file) => MEDIUMSIZE;
 
@@ -196,26 +212,30 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # truncate write under powerloss
-define.SMALLSIZE = [4, 512]
-define.MEDIUMSIZE = [32, 1024]
-define.LARGESIZE = 2048
+# truncate write under powerloss
+[cases.reentrant_truncate_write]
+defines.SMALLSIZE = [4, 512]
+defines.MEDIUMSIZE = [32, 1024]
+defines.LARGESIZE = 2048
 reentrant = true
 code = '''
-    err = lfs_mount(&lfs, &cfg);
+    lfs_t lfs;
+    int err = lfs_mount(&lfs, cfg);
     if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
+        lfs_format(&lfs, cfg) => 0;
+        lfs_mount(&lfs, cfg) => 0;
     }
+    lfs_file_t file;
     err = lfs_file_open(&lfs, &file, "baldy", LFS_O_RDONLY);
     assert(!err || err == LFS_ERR_NOENT);
     if (!err) {
-        size = lfs_file_size(&lfs, &file);
+        size_t size = lfs_file_size(&lfs, &file);
         assert(size == 0 ||
-                size == LARGESIZE ||
-                size == MEDIUMSIZE ||
-                size == SMALLSIZE);
+                size == (size_t)LARGESIZE ||
+                size == (size_t)MEDIUMSIZE ||
+                size == (size_t)SMALLSIZE);
         for (lfs_off_t j = 0; j < size; j += 4) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &file, buffer, 4) => 4;
             assert(memcmp(buffer, "hair", 4) == 0 ||
                    memcmp(buffer, "bald", 4) == 0 ||
@@ -227,8 +247,9 @@ code = '''
     lfs_file_open(&lfs, &file, "baldy",
             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
     lfs_file_size(&lfs, &file) => 0;
+    uint8_t buffer[1024];
     strcpy((char*)buffer, "hair");
-    size = strlen((char*)buffer);
+    size_t size = strlen((char*)buffer);
     for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
     }
@@ -262,12 +283,14 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # more aggressive general truncation tests
-define.CONFIG = 'range(6)'
-define.SMALLSIZE = 32
-define.MEDIUMSIZE = 2048
-define.LARGESIZE = 8192
+# more aggressive general truncation tests
+[cases.aggressive_truncate]
+defines.CONFIG = [0,1,2,3,4,5]
+defines.SMALLSIZE = 32
+defines.MEDIUMSIZE = 2048
+defines.LARGESIZE = 8192
 code = '''
+    lfs_t lfs;
     #define COUNT 5
     const struct {
         lfs_off_t startsizes[COUNT];
@@ -312,16 +335,19 @@ code = '''
     const lfs_off_t *hotsizes   = configs[CONFIG].hotsizes;
     const lfs_off_t *coldsizes  = configs[CONFIG].coldsizes;
 
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     for (unsigned i = 0; i < COUNT; i++) {
+        char path[1024];
         sprintf(path, "hairyhead%d", i);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 
+        uint8_t buffer[1024];
         strcpy((char*)buffer, "hair");
-        size = strlen((char*)buffer);
+        size_t size = strlen((char*)buffer);
         for (lfs_off_t j = 0; j < startsizes[i]; j += size) {
             lfs_file_write(&lfs, &file, buffer, size) => size;
         }
@@ -340,21 +366,25 @@ code = '''
 
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     for (unsigned i = 0; i < COUNT; i++) {
+        char path[1024];
         sprintf(path, "hairyhead%d", i);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path, LFS_O_RDWR) => 0;
         lfs_file_size(&lfs, &file) => hotsizes[i];
 
-        size = strlen("hair");
+        size_t size = strlen("hair");
         lfs_off_t j = 0;
         for (; j < startsizes[i] && j < hotsizes[i]; j += size) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &file, buffer, size) => size;
             memcmp(buffer, "hair", size) => 0;
         }
 
         for (; j < hotsizes[i]; j += size) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &file, buffer, size) => size;
             memcmp(buffer, "\0\0\0\0", size) => 0;
         }
@@ -367,22 +397,26 @@ code = '''
 
     lfs_unmount(&lfs) => 0;
 
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
 
     for (unsigned i = 0; i < COUNT; i++) {
+        char path[1024];
         sprintf(path, "hairyhead%d", i);
+        lfs_file_t file;
         lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
         lfs_file_size(&lfs, &file) => coldsizes[i];
 
-        size = strlen("hair");
+        size_t size = strlen("hair");
         lfs_off_t j = 0;
         for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i];
                 j += size) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &file, buffer, size) => size;
             memcmp(buffer, "hair", size) => 0;
         }
 
         for (; j < coldsizes[i]; j += size) {
+            uint8_t buffer[1024];
             lfs_file_read(&lfs, &file, buffer, size) => size;
             memcmp(buffer, "\0\0\0\0", size) => 0;
         }
@@ -393,16 +427,20 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
-[[case]] # noop truncate
-define.MEDIUMSIZE = [32, 2048]
+# noop truncate
+[cases.nop_truncate]
+defines.MEDIUMSIZE = [32, 2048]
 code = '''
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
     lfs_file_open(&lfs, &file, "baldynoop",
             LFS_O_RDWR | LFS_O_CREAT) => 0;
 
+    uint8_t buffer[1024];
     strcpy((char*)buffer, "hair");
-    size = strlen((char*)buffer);
+    size_t size = strlen((char*)buffer);
     for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
         lfs_file_write(&lfs, &file, buffer, size) => size;
 
@@ -426,7 +464,7 @@ code = '''
     lfs_unmount(&lfs) => 0;
 
     // still there after reboot?
-    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
     lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDWR) => 0;
     lfs_file_size(&lfs, &file) => MEDIUMSIZE;
     for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {

部分文件因为文件数量过多而无法显示