소스 검색

Migrated test_files, test_dirs, test_format suites to new framework

Also some tweaks to test_.py to capture Makefile warnings and print
test locations a bit better.
Christopher Haster 6 년 전
부모
커밋
1d2688a771
4개의 변경된 파일1172개의 추가작업 그리고 53개의 파일을 삭제
  1. 43 11
      scripts/test_.py
  2. 538 42
      tests_/test_dirs.toml
  3. 486 0
      tests_/test_files.toml
  4. 105 0
      tests_/test_format.toml

+ 43 - 11
scripts/test_.py

@@ -62,8 +62,10 @@ PROLOGUE = """
     __attribute__((unused)) lfs_file_t file;
     __attribute__((unused)) lfs_dir_t dir;
     __attribute__((unused)) struct lfs_info info;
-    __attribute__((unused)) uint8_t buffer[1024];
     __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 = LFS_DISK ? (void*)&filebd : (void*)&rambd,
@@ -124,9 +126,11 @@ class TestCase:
 
     def __str__(self):
         if hasattr(self, 'permno'):
-            return '%s[%d,%d]' % (self.suite.name, self.caseno, self.permno)
+            return '%s[%d,%d]' % (
+                self.suite.name, self.caseno, self.permno)
         else:
-            return '%s[%d]' % (self.suite.name, self.caseno)
+            return '%s[%d]' % (
+                self.suite.name, self.caseno)
 
     def permute(self, defines, permno=None, **_):
         ncase = copy.copy(self)
@@ -211,7 +215,10 @@ class TestCase:
             if args.get('verbose', False):
                 sys.stdout.write(line)
             # intercept asserts
-            m = re.match('^([^:]+):([0-9]+):(assert): (.*)$', line)
+            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:
@@ -221,7 +228,7 @@ class TestCase:
                         'path': m.group(1),
                         'line': line,
                         'lineno': lineno,
-                        'message': m.group(4)}
+                        'message': m.group(3)}
                 except:
                     pass
         proc.wait()
@@ -385,7 +392,7 @@ class TestSuite:
 
         self.defines = {}
         for k, v in self.perms[0].defines.items():
-            if all(perm.defines[k] == v for perm in self.perms):
+            if all(perm.defines.get(k, None) == v for perm in self.perms):
                 self.defines[k] = v
 
         return self.perms
@@ -401,7 +408,7 @@ class TestSuite:
 
         f.write('\n')
         f.write('int main(int argc, char **argv) {\n')
-        f.write(4*' '+'int case_ = (argc >= 3) ? atoi(argv[1]) : 0;\n')
+        f.write(4*' '+'int case_ = (argc >= 2) ? atoi(argv[1]) : 0;\n')
         f.write(4*' '+'int perm = (argc >= 3) ? atoi(argv[2]) : 0;\n')
         f.write(4*' '+'LFS_DISK = (argc >= 4) ? argv[3] : NULL;\n')
         for perm in self.perms:
@@ -544,6 +551,23 @@ def main(**args):
         stdout.append(line)
         if args.get('verbose', False):
             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', False):
+            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
     proc.wait()
 
     if proc.returncode != 0:
@@ -587,12 +611,20 @@ def main(**args):
             if perm.result == PASS:
                 passed += 1
             else:
-                sys.stdout.write("--- %s ---\n" % perm)
-                if perm.result.assert_:
-                    for line in perm.result.stdout[:-1]:
+                #sys.stdout.write("--- %s ---\n" % perm)
+                sys.stdout.write(
+                    "\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m "
+                    "{perm} failed with {returncode}\n".format(
+                        perm=perm, path=perm.suite.path, lineno=perm.lineno-2,
+                        returncode=perm.result.returncode or 0))
+                if perm.result.stdout:
+                    for line in (perm.result.stdout
+                            if not perm.result.assert_
+                            else perm.result.stdout[:-1]):
                         sys.stdout.write(line)
+                if perm.result.assert_:
                     sys.stdout.write(
-                        "\033[97m{path}:{lineno}:\033[91massert:\033[0m "
+                        "\033[01m{path}:{lineno}:\033[01;31massert:\033[m "
                         "{message}\n{line}\n".format(
                             **perm.result.assert_))
                 else:

+ 538 - 42
tests_/test_dirs.toml

@@ -1,28 +1,5 @@
-[[case]] # format
-code = """
-    lfs_format(&lfs, &cfg) => 0;
-"""
-
-[[case]] # mount/unmount
-code = """
-    lfs_format(&lfs, &cfg) => 0;
-    lfs_mount(&lfs, &cfg) => 0;
-    lfs_unmount(&lfs) => 0;
-"""
-
-[[case]] # reentrant format
-code = """
-    int err = lfs_mount(&lfs, &cfg);
-    if (err) {
-        lfs_format(&lfs, &cfg) => 0;
-        lfs_mount(&lfs, &cfg) => 0;
-    }
-    lfs_unmount(&lfs) => 0;
-"""
-reentrant = true
-
 [[case]] # root
-code = """
+code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
@@ -35,10 +12,10 @@ code = """
     lfs_dir_read(&lfs, &dir, &info) => 0;
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
-"""
+'''
 
-[[case]] # directory creation
-code = """
+[[case]] # many directory creation
+code = '''
     lfs_format(&lfs, &cfg) => 0;
 
     lfs_mount(&lfs, &cfg) => 0;
@@ -65,11 +42,11 @@ code = """
     lfs_dir_read(&lfs, &dir, &info) => 0;
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
-"""
+'''
 define.N = 'range(0, 100, 3)'
 
-[[case]] # directory removal
-code = """
+[[case]] # many directory removal
+code = '''
     lfs_format(&lfs, &cfg) => 0;
 
     lfs_mount(&lfs, &cfg) => 0;
@@ -115,19 +92,159 @@ code = """
     lfs_dir_read(&lfs, &dir, &info) => 0;
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
-"""
+'''
+define.N = 'range(3, 100, 11)'
+
+[[case]] # many directory rename
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "test%03d", i);
+        lfs_mkdir(&lfs, path) => 0;
+    }
+    lfs_unmount(&lfs) => 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);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "test%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs);
+
+    lfs_mount(&lfs, &cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        char oldpath[128];
+        char newpath[128];
+        sprintf(oldpath, "test%03d", i);
+        sprintf(newpath, "tedd%03d", i);
+        lfs_rename(&lfs, oldpath, newpath) => 0;
+    }
+    lfs_unmount(&lfs);
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "tedd%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs);
+'''
 define.N = 'range(3, 100, 11)'
 
+[[case]] # reentrant many directory creation/rename/removal
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hi%03d", i);
+        err = lfs_mkdir(&lfs, path);
+        assert(err == 0 || err == LFS_ERR_EXIST);
+    }
+
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hello%03d", i);
+        err = lfs_remove(&lfs, path);
+        assert(err == 0 || err == LFS_ERR_NOENT);
+    }
+
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hi%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    for (int i = 0; i < N; i++) {
+        char oldpath[128];
+        char newpath[128];
+        sprintf(oldpath, "hi%03d", i);
+        sprintf(newpath, "hello%03d", i);
+        // YES this can overwrite an existing newpath
+        lfs_rename(&lfs, oldpath, newpath) => 0;
+    }
+
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hello%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hello%03d", i);
+        lfs_remove(&lfs, path) => 0;
+    }
+
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+define.N = [5, 25]
+reentrant = true
+
 [[case]] # file creation
-code = """
+code = '''
     lfs_format(&lfs, &cfg) => 0;
 
     lfs_mount(&lfs, &cfg) => 0;
     for (int i = 0; i < N; i++) {
         sprintf(path, "file%03d", i);
-        lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0;
+        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;
@@ -147,17 +264,18 @@ code = """
     lfs_dir_read(&lfs, &dir, &info) => 0;
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs);
-"""
+'''
 define.N = 'range(3, 100, 11)'
 
 [[case]] # file removal
-code = """
+code = '''
     lfs_format(&lfs, &cfg) => 0;
 
     lfs_mount(&lfs, &cfg) => 0;
     for (int i = 0; i < N; i++) {
         sprintf(path, "removeme%03d", i);
-        lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0;
+        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;
@@ -198,23 +316,401 @@ code = """
     lfs_dir_read(&lfs, &dir, &info) => 0;
     lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
-"""
+'''
+define.N = 'range(0, 100, 3)'
+
+[[case]] # file rename
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "test%03d", i);
+        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_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "test%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_REG);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs);
+
+    lfs_mount(&lfs, &cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        char oldpath[128];
+        char newpath[128];
+        sprintf(oldpath, "test%03d", i);
+        sprintf(newpath, "tedd%03d", i);
+        lfs_rename(&lfs, oldpath, newpath) => 0;
+    }
+    lfs_unmount(&lfs);
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "tedd%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_REG);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs);
+'''
 define.N = 'range(0, 100, 3)'
 
-[[case]] # error cases
-code = """
+[[case]] # reentrant file creation/rename/removal
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hi%03d", i);
+        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++) {
+        sprintf(path, "hello%03d", i);
+        err = lfs_remove(&lfs, path);
+        assert(err == 0 || err == LFS_ERR_NOENT);
+    }
+
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hi%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_REG);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    for (int i = 0; i < N; i++) {
+        char oldpath[128];
+        char newpath[128];
+        sprintf(oldpath, "hi%03d", i);
+        sprintf(newpath, "hello%03d", i);
+        // YES this can overwrite an existing newpath
+        lfs_rename(&lfs, oldpath, newpath) => 0;
+    }
+
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hello%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_REG);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "hello%03d", i);
+        lfs_remove(&lfs, path) => 0;
+    }
+
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+define.N = [5, 25]
+reentrant = true
+
+[[case]] # nested directories
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "potato") => 0;
+    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_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_dir_open(&lfs, &dir, "potato") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "baked") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "fried") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "sweet") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // try removing?
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_remove(&lfs, "potato") => LFS_ERR_NOTEMPTY;
+    lfs_unmount(&lfs) => 0;
+
+    // try renaming?
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "potato", "coldpotato") => 0;
+    lfs_unmount(&lfs) => 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_remove(&lfs, "potato") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "warmpotato") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
+    lfs_unmount(&lfs) => 0;
+
+    // try cross-directory renaming
+    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;
+    lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY;
+    lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
+    lfs_rename(&lfs, "hotpotato/fried", "coldpotato/fried") => 0;
+    lfs_rename(&lfs, "coldpotato", "hotpotato") => LFS_ERR_NOTEMPTY;
+    lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY;
+    lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
+    lfs_rename(&lfs, "hotpotato/sweet", "coldpotato/sweet") => 0;
+    lfs_rename(&lfs, "coldpotato", "hotpotato") => 0;
+    lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
+    lfs_unmount(&lfs) => 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);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "baked") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "fried") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "sweet") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+    
+    // final remove
+    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;
+    lfs_remove(&lfs, "hotpotato/fried") => 0;
+    lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
+    lfs_remove(&lfs, "hotpotato/sweet") => 0;
+    lfs_remove(&lfs, "hotpotato") => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, ".") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "..") == 0);
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(strcmp(info.name, "burito") == 0);
+    info.type => LFS_TYPE_REG;
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # recursive remove
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "prickly-pear") => 0;
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "prickly-pear/cactus%03d", i);
+        lfs_mkdir(&lfs, path) => 0;
+    }
+    lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "cactus%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs);
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY;
+
+    lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        sprintf(path, "cactus%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        assert(strcmp(info.name, path) == 0);
+        sprintf(path, "prickly-pear/%s", info.name);
+        lfs_remove(&lfs, path) => 0;
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_remove(&lfs, "prickly-pear") => 0;
+    lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+'''
+define.N = [10, 100]
+
+[[case]] # other error cases
+code = '''
     lfs_format(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mkdir(&lfs, "potato") => 0;
-    lfs_file_open(&lfs, &file, "burito", LFS_O_CREAT | LFS_O_WRONLY) => 0;
+    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_mkdir(&lfs, "potato") => LFS_ERR_EXIST;
+    lfs_mkdir(&lfs, "burito") => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, "burito",
+            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_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;
     lfs_file_open(&lfs, &file, "potato", LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "tomato", LFS_O_WRONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "potato", LFS_O_WRONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "potato",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+    lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, "/",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, "/", LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "/", LFS_O_WRONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "/",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+    // check that errors did not corrupt directory
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_REG);
+    assert(strcmp(info.name, "burito") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "potato") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_unmount(&lfs) => 0;
+
+    // or on disk
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_REG);
+    assert(strcmp(info.name, "burito") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "potato") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
     lfs_unmount(&lfs) => 0;
-"""
+'''

+ 486 - 0
tests_/test_files.toml

@@ -0,0 +1,486 @@
+
+[[case]] # simple file test
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "hello",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    size = strlen("Hello World!")+1;
+    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_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file, buffer, size) => size;
+    assert(strcmp((char*)buffer, "Hello World!") == 0);
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # larger files
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+
+    // write
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    srand(1);
+    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++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // read
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
+    lfs_file_size(&lfs, &file) => SIZE;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+define.SIZE = [32, 8192, 262144, 0, 7, 8193]
+define.CHUNKSIZE = [31, 16, 33, 1, 1023]
+
+[[case]] # rewriting files
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+
+    // write
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // read
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
+    lfs_file_size(&lfs, &file) => SIZE1;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // rewrite
+    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) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // read
+    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);
+    for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    if (SIZE1 > SIZE2) {
+        srand(1);
+        for (lfs_size_t b = 0; b < SIZE2; b++) {
+            rand();
+        }
+        for (lfs_size_t i = SIZE2; i < SIZE1; i += CHUNKSIZE) {
+            lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
+            lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+            for (lfs_size_t b = 0; b < chunk; b++) {
+                assert(buffer[b] == (rand() & 0xff));
+            }
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
+define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
+define.CHUNKSIZE = [31, 16, 1, 1025]
+
+[[case]] # appending files
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+
+    // write
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // read
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
+    lfs_file_size(&lfs, &file) => SIZE1;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // append
+    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) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // read
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
+    lfs_file_size(&lfs, &file) => SIZE1 + SIZE2;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    srand(2);
+    for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
+define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
+define.CHUNKSIZE = [31, 16, 1, 1025]
+
+[[case]] # truncating files
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+
+    // write
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // read
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
+    lfs_file_size(&lfs, &file) => SIZE1;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // truncate
+    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) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // read
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
+    lfs_file_size(&lfs, &file) => SIZE2;
+    srand(2);
+    for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
+define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
+define.CHUNKSIZE = [31, 16, 1, 1025]
+
+[[case]] # reentrant file writing
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+
+    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);
+        assert(size == 0 || size == SIZE);
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // write
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    srand(1);
+    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++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+
+    // read
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
+    lfs_file_size(&lfs, &file) => SIZE;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+define.SIZE = [32, 0, 7, 2049]
+define.CHUNKSIZE = [31, 16, 65]
+reentrant = true
+
+[[case]] # reentrant file writing with syncs
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+
+    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);
+        assert(size <= SIZE);
+        srand(1);
+        for (lfs_size_t i = 0; i < size; i += CHUNKSIZE) {
+            lfs_size_t chunk = lfs_min(CHUNKSIZE, size-i);
+            lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+            for (lfs_size_t b = 0; b < chunk; b++) {
+                assert(buffer[b] == (rand() & 0xff));
+            }
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // write
+    lfs_file_open(&lfs, &file, "avacado",
+        LFS_O_WRONLY | LFS_O_CREAT | MODE) => 0;
+    size = lfs_file_size(&lfs, &file);
+    assert(size <= SIZE);
+    srand(1);
+    lfs_size_t skip = (MODE == LFS_O_APPEND) ? size : 0;
+    for (lfs_size_t b = 0; b < skip; b++) {
+        rand();
+    }
+    for (lfs_size_t i = skip; i < SIZE; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            buffer[b] = rand() & 0xff;
+        }
+        lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
+        lfs_file_sync(&lfs, &file) => 0;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+
+    // read
+    lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
+    lfs_file_size(&lfs, &file) => SIZE;
+    srand(1);
+    for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
+        lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
+        lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
+        for (lfs_size_t b = 0; b < chunk; b++) {
+            assert(buffer[b] == (rand() & 0xff));
+        }
+    }
+    lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+define = [
+    # append (O(n))
+    {MODE='LFS_O_APPEND',   SIZE=[32, 0, 7, 2049],  CHUNKSIZE=[31, 16, 65]},
+    # truncate (O(n^2))
+    {MODE='LFS_O_TRUNC',    SIZE=[32, 0, 7, 200],   CHUNKSIZE=[31, 16, 65]},
+    # rewrite (O(n^2))
+    {MODE=0,                SIZE=[32, 0, 7, 200],   CHUNKSIZE=[31, 16, 65]},
+]
+reentrant = true
+
+[[case]] # many files
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    // create N files of 7 bytes
+    lfs_mount(&lfs, &cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        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_file_write(&lfs, &file, wbuffer, size) => size;
+        lfs_file_close(&lfs, &file) => 0;
+
+        char rbuffer[1024];
+        lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
+        lfs_file_read(&lfs, &file, rbuffer, size) => size;
+        assert(strcmp(rbuffer, wbuffer) == 0);
+        lfs_file_close(&lfs, &file) => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+'''
+define.N = 300
+
+[[case]] # many files with power cycle
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    // create N files of 7 bytes
+    lfs_mount(&lfs, &cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        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_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_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
+        lfs_file_read(&lfs, &file, rbuffer, size) => size;
+        assert(strcmp(rbuffer, wbuffer) == 0);
+        lfs_file_close(&lfs, &file) => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+'''
+define.N = 300
+
+[[case]] # many files with power loss
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+    // create N files of 7 bytes
+    for (int i = 0; i < N; i++) {
+        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);
+        if ((lfs_size_t)lfs_file_size(&lfs, &file) != size) {
+            lfs_file_write(&lfs, &file, wbuffer, size) => size;
+        }
+        lfs_file_close(&lfs, &file) => 0;
+
+        char rbuffer[1024];
+        lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
+        lfs_file_read(&lfs, &file, rbuffer, size) => size;
+        assert(strcmp(rbuffer, wbuffer) == 0);
+        lfs_file_close(&lfs, &file) => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+'''
+define.N = 300
+reentrant = true

+ 105 - 0
tests_/test_format.toml

@@ -0,0 +1,105 @@
+[[case]] # simple formatting test
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+'''
+
+[[case]] # mount/unmount
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[[case]] # reentrant format
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+'''
+reentrant = true
+
+[[case]] # invalid mount
+code = '''
+    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
+'''
+
+# TODO invalid superblock? (corrupt 1, 0)
+
+[[case]] # expanding superblock
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        lfs_mkdir(&lfs, "dummy") => 0;
+        lfs_stat(&lfs, "dummy", &info) => 0;
+        assert(strcmp(info.name, "dummy") == 0);
+        lfs_remove(&lfs, "dummy") => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+
+    // one last check after power-cycle
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "dummy") => 0;
+    lfs_stat(&lfs, "dummy", &info) => 0;
+    assert(strcmp(info.name, "dummy") == 0);
+    lfs_unmount(&lfs) => 0;
+'''
+define.BLOCK_CYCLES = [32, 33, 1]
+define.N = [10, 100, 1000]
+
+[[case]] # expanding superblock with power cycle
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        lfs_mount(&lfs, &cfg) => 0;
+        // remove lingering dummy?
+        err = lfs_remove(&lfs, "dummy");
+        assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
+        
+        lfs_mkdir(&lfs, "dummy") => 0;
+        lfs_stat(&lfs, "dummy", &info) => 0;
+        assert(strcmp(info.name, "dummy") == 0);
+        lfs_unmount(&lfs) => 0;
+    }
+
+    // one last check after power-cycle
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_stat(&lfs, "dummy", &info) => 0;
+    assert(strcmp(info.name, "dummy") == 0);
+    lfs_unmount(&lfs) => 0;
+'''
+define.BLOCK_CYCLES = [32, 33, 1]
+define.N = [10, 100, 1000]
+
+[[case]] # reentrant expanding superblock
+code = '''
+    err = lfs_mount(&lfs, &cfg);
+    if (err) {
+        lfs_format(&lfs, &cfg) => 0;
+        lfs_mount(&lfs, &cfg) => 0;
+    }
+
+    for (int i = 0; i < N; i++) {
+        // remove lingering dummy?
+        err = lfs_remove(&lfs, "dummy");
+        assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
+        
+        lfs_mkdir(&lfs, "dummy") => 0;
+        lfs_stat(&lfs, "dummy", &info) => 0;
+        assert(strcmp(info.name, "dummy") == 0);
+    }
+
+    lfs_unmount(&lfs) => 0;
+
+    // one last check after power-cycle
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_stat(&lfs, "dummy", &info) => 0;
+    assert(strcmp(info.name, "dummy") == 0);
+    lfs_unmount(&lfs) => 0;
+'''
+define.BLOCK_CYCLES = [2, 1]
+define.N = 24
+reentrant = true