Эх сурвалжийг харах

Added tests for forwards and backwards disk compatibility

This is a bit tricky since we need two different version of littlefs in
order to test for most compatibility concerns.

Fortunately we already have scripts/changeprefix.py for version-specific
symbols, so it's not that hard to link in the previous version of
littlefs in CI as a separate set of symbols, "lfsp_" in this case.

So that we can at least test the compatibility tests locally, I've added
an ifdef against the expected define "LFSP" to define a set of aliases
mapping "lfsp_" symbols to "lfs_" symbols. This is manual at the moment,
and a bit hacky, but gets the job done.

---

Also changed BUILDDIR creation to derive subdirectories from a few
Makefile variables. This makes the subdirectories less manual and more
flexible for things like LFSP. Note this wasn't possible until BUILDDIR
was changed to default to "." when omitted.
Christopher Haster 2 жил өмнө
parent
commit
116332d3f7
2 өөрчлөгдсөн 1294 нэмэгдсэн , 11 устгасан
  1. 16 11
      Makefile
  2. 1278 0
      tests/test_compat.toml

+ 16 - 11
Makefile

@@ -1,15 +1,5 @@
-ifdef BUILDDIR
-# bit of a hack, but we want to make sure BUILDDIR directory structure
-# is correct before any commands
-$(if $(findstring n,$(MAKEFLAGS)),, $(shell mkdir -p \
-	$(BUILDDIR)/ \
-	$(BUILDDIR)/bd \
-	$(BUILDDIR)/runners \
-	$(BUILDDIR)/tests \
-	$(BUILDDIR)/benches))
-endif
+# overrideable build dir, default is in-place
 BUILDDIR ?= .
-
 # overridable target/src/tools/flags/etc
 ifneq ($(wildcard test.c main.c),)
 TARGET ?= $(BUILDDIR)/lfs
@@ -163,6 +153,18 @@ TESTFLAGS  += --perf-path="$(PERF)"
 BENCHFLAGS += --perf-path="$(PERF)"
 endif
 
+# this is a bit of a hack, but we want to make sure the BUILDDIR
+# directory structure is correct before we run any commands
+ifneq ($(BUILDDIR),.)
+$(if $(findstring n,$(MAKEFLAGS)),, $(shell mkdir -p \
+	$(addprefix $(BUILDDIR)/,$(dir \
+		$(SRC) \
+		$(TESTS) \
+		$(TEST_SRC) \
+		$(BENCHES) \
+		$(BENCH_SRC)))))
+endif
+
 
 # commands
 
@@ -514,6 +516,9 @@ $(BUILDDIR)/runners/bench_runner: $(BENCH_OBJ)
 $(BUILDDIR)/%.o $(BUILDDIR)/%.ci: %.c
 	$(CC) -c -MMD $(CFLAGS) $< -o $(BUILDDIR)/$*.o
 
+$(BUILDDIR)/%.o $(BUILDDIR)/%.ci: $(BUILDDIR)/%.c
+	$(CC) -c -MMD $(CFLAGS) $< -o $(BUILDDIR)/$*.o
+
 $(BUILDDIR)/%.s: %.c
 	$(CC) -S $(CFLAGS) $< -o $@
 

+ 1278 - 0
tests/test_compat.toml

@@ -0,0 +1,1278 @@
+# Test for compatibility between different littlefs versions
+#
+# Note, these tests are a bit special. They expect to be linked against two
+# different versions of littlefs:
+# - lfs  => the new/current version of littlefs
+# - lfsp => the previous version of littlefs
+#
+# If lfsp is not linked, and LFSP is not defined, these tests will alias
+# the relevant lfs types/functions as necessary so at least the tests can
+# themselves be tested locally.
+#
+# But to get value from these tests, it's expected that the previous version
+# of littlefs be linked in during CI, with the help of scripts/changeprefix.py
+#
+
+# alias littlefs symbols as needed
+#
+# there may be a better way to do this, but oh well, explicit aliases works
+code = '''
+#ifdef LFSP
+#define STRINGIZE(x) STRINGIZE_(x)
+#define STRINGIZE_(x) #x
+#include STRINGIZE(LFSP)
+#else
+#define LFSP_VERSION LFS_VERSION
+#define LFSP_VERSION_MAJOR LFS_VERSION_MAJOR
+#define LFSP_VERSION_MINOR LFS_VERSION_MINOR
+#define lfsp_t lfs_t
+#define lfsp_config lfs_config
+#define lfsp_format lfs_format
+#define lfsp_mount lfs_mount
+#define lfsp_unmount lfs_unmount
+#define lfsp_dir_t lfs_dir_t
+#define lfsp_info lfs_info
+#define LFSP_TYPE_REG LFS_TYPE_REG
+#define LFSP_TYPE_DIR LFS_TYPE_DIR
+#define lfsp_mkdir lfs_mkdir
+#define lfsp_dir_open lfs_dir_open
+#define lfsp_dir_read lfs_dir_read
+#define lfsp_dir_close lfs_dir_close
+#define lfsp_file_t lfs_file_t
+#define LFSP_O_RDONLY LFS_O_RDONLY
+#define LFSP_O_WRONLY LFS_O_WRONLY
+#define LFSP_O_CREAT LFS_O_CREAT
+#define LFSP_O_EXCL LFS_O_EXCL
+#define LFSP_SEEK_SET LFS_SEEK_SET
+#define lfsp_file_open lfs_file_open
+#define lfsp_file_write lfs_file_write
+#define lfsp_file_read lfs_file_read
+#define lfsp_file_seek lfs_file_seek
+#define lfsp_file_close lfs_file_close
+#endif
+'''
+
+
+
+## forward-compatibility tests ##
+
+# test we can mount in a new version
+[cases.test_compat_forward_mount]
+if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+code = '''
+    // create the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_format(&lfsp, &cfgp) => 0;
+
+    // confirm the previous mount works
+    lfsp_mount(&lfsp, &cfgp) => 0;
+    lfsp_unmount(&lfsp) => 0;
+
+
+    // now test the new mount
+    lfs_t lfs;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test we can read dirs in a new version
+[cases.test_compat_forward_read_dirs]
+defines.COUNT = 5
+if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+code = '''
+    // create the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_format(&lfsp, &cfgp) => 0;
+
+    // write COUNT dirs
+    lfsp_mount(&lfsp, &cfgp) => 0;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfsp_mkdir(&lfsp, name) => 0;
+    }
+    lfsp_unmount(&lfsp) => 0;
+
+
+    // mount the new version
+    lfs_t lfs;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // can we list the directories?
+    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);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        assert(strcmp(info.name, name) == 0);
+    }
+
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test we can read files in a new version
+[cases.test_compat_forward_read_files]
+defines.COUNT = 5
+defines.SIZE = [4, 32, 512, 8192]
+defines.CHUNK = 4
+if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+code = '''
+    // create the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_format(&lfsp, &cfgp) => 0;
+
+    // write COUNT files
+    lfsp_mount(&lfsp, &cfgp) => 0;
+    uint32_t prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfsp_file_open(&lfsp, &file, name,
+                LFSP_O_WRONLY | LFSP_O_CREAT | LFSP_O_EXCL) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+    }
+    lfsp_unmount(&lfsp) => 0;
+
+
+    // mount the new version
+    lfs_t lfs;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // can we list the files?
+    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);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_REG);
+        char name[8];
+        sprintf(name, "file%03d", i);
+        assert(strcmp(info.name, name) == 0);
+        assert(info.size == SIZE);
+    }
+
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+
+    // now can we read the files?
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            lfs_file_read(&lfs, &file, chunk, CHUNK) => CHUNK;
+
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                assert(chunk[k] == TEST_PRNG(&prng) & 0xff);
+            }
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test we can read files in dirs in a new version
+[cases.test_compat_forward_read_files_in_dirs]
+defines.COUNT = 5
+defines.SIZE = [4, 32, 512, 8192]
+defines.CHUNK = 4
+if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+code = '''
+    // create the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_format(&lfsp, &cfgp) => 0;
+
+    // write COUNT files+dirs
+    lfsp_mount(&lfsp, &cfgp) => 0;
+    uint32_t prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[16];
+        sprintf(name, "dir%03d", i);
+        lfsp_mkdir(&lfsp, name) => 0;
+
+        lfsp_file_t file;
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfsp_file_open(&lfsp, &file, name,
+                LFSP_O_WRONLY | LFSP_O_CREAT | LFSP_O_EXCL) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+    }
+    lfsp_unmount(&lfsp) => 0;
+
+
+    // mount the new version
+    lfs_t lfs;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // can we list the directories?
+    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);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        assert(strcmp(info.name, name) == 0);
+    }
+
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    // can we list the files?
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, name) => 0;
+        struct lfs_info info;
+        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);
+        sprintf(name, "file%03d", i);
+        assert(strcmp(info.name, name) == 0);
+        assert(info.size == SIZE);
+
+        lfs_dir_read(&lfs, &dir, &info) => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    }
+
+    // now can we read the files?
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_file_t file;
+        char name[16];
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            lfs_file_read(&lfs, &file, chunk, CHUNK) => CHUNK;
+
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                assert(chunk[k] == TEST_PRNG(&prng) & 0xff);
+            }
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test we can write dirs in a new version
+[cases.test_compat_forward_write_dirs]
+defines.COUNT = 10
+if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+code = '''
+    // create the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_format(&lfsp, &cfgp) => 0;
+
+    // write COUNT/2 dirs
+    lfsp_mount(&lfsp, &cfgp) => 0;
+    for (lfs_size_t i = 0; i < COUNT/2; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfsp_mkdir(&lfsp, name) => 0;
+    }
+    lfsp_unmount(&lfsp) => 0;
+
+
+    // mount the new version
+    lfs_t lfs;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // write another COUNT/2 dirs
+    for (lfs_size_t i = COUNT/2; i < COUNT; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfs_mkdir(&lfs, name) => 0;
+    }
+
+    // can we list the directories?
+    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);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        assert(strcmp(info.name, name) == 0);
+    }
+
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test we can write files in a new version
+[cases.test_compat_forward_write_files]
+defines.COUNT = 5
+defines.SIZE = [4, 32, 512, 8192]
+defines.CHUNK = 2
+if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+code = '''
+    // create the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_format(&lfsp, &cfgp) => 0;
+
+    // write half COUNT files
+    lfsp_mount(&lfsp, &cfgp) => 0;
+    uint32_t prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        // write half
+        lfsp_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfsp_file_open(&lfsp, &file, name,
+                LFSP_O_WRONLY | LFSP_O_CREAT | LFSP_O_EXCL) => 0;
+        for (lfs_size_t j = 0; j < SIZE/2; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+
+        // skip the other half but keep our prng reproducible
+        for (lfs_size_t j = SIZE/2; j < SIZE; j++) {
+            TEST_PRNG(&prng);
+        }
+    }
+    lfsp_unmount(&lfsp) => 0;
+
+
+    // mount the new version
+    lfs_t lfs;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // write half COUNT files
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        // skip half but keep our prng reproducible
+        for (lfs_size_t j = 0; j < SIZE/2; j++) {
+            TEST_PRNG(&prng);
+        }
+
+        // write the other half
+        lfs_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfs_file_open(&lfs, &file, name, LFS_O_WRONLY) => 0;
+        lfs_file_seek(&lfs, &file, SIZE/2, LFS_SEEK_SET) => SIZE/2;
+
+        for (lfs_size_t j = SIZE/2; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // can we list the files?
+    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);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_REG);
+        char name[8];
+        sprintf(name, "file%03d", i);
+        assert(strcmp(info.name, name) == 0);
+        assert(info.size == SIZE);
+    }
+
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+
+    // now can we read the files?
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            lfs_file_read(&lfs, &file, chunk, CHUNK) => CHUNK;
+
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                assert(chunk[k] == TEST_PRNG(&prng) & 0xff);
+            }
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test we can write files in dirs in a new version
+[cases.test_compat_forward_write_files_in_dirs]
+defines.COUNT = 5
+defines.SIZE = [4, 32, 512, 8192]
+defines.CHUNK = 2
+if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+code = '''
+    // create the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_format(&lfsp, &cfgp) => 0;
+
+    // write half COUNT files
+    lfsp_mount(&lfsp, &cfgp) => 0;
+    uint32_t prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[16];
+        sprintf(name, "dir%03d", i);
+        lfsp_mkdir(&lfsp, name) => 0;
+
+        // write half
+        lfsp_file_t file;
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfsp_file_open(&lfsp, &file, name,
+                LFSP_O_WRONLY | LFSP_O_CREAT | LFSP_O_EXCL) => 0;
+        for (lfs_size_t j = 0; j < SIZE/2; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+
+        // skip the other half but keep our prng reproducible
+        for (lfs_size_t j = SIZE/2; j < SIZE; j++) {
+            TEST_PRNG(&prng);
+        }
+    }
+    lfsp_unmount(&lfsp) => 0;
+
+
+    // mount the new version
+    lfs_t lfs;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // write half COUNT files
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        // skip half but keep our prng reproducible
+        for (lfs_size_t j = 0; j < SIZE/2; j++) {
+            TEST_PRNG(&prng);
+        }
+
+        // write the other half
+        lfs_file_t file;
+        char name[16];
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfs_file_open(&lfs, &file, name, LFS_O_WRONLY) => 0;
+        lfs_file_seek(&lfs, &file, SIZE/2, LFS_SEEK_SET) => SIZE/2;
+
+        for (lfs_size_t j = SIZE/2; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // can we list the directories?
+    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);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        assert(strcmp(info.name, name) == 0);
+    }
+
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    // can we list the files?
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, name) => 0;
+        struct lfs_info info;
+        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);
+        sprintf(name, "file%03d", i);
+        assert(strcmp(info.name, name) == 0);
+        assert(info.size == SIZE);
+
+        lfs_dir_read(&lfs, &dir, &info) => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    }
+
+    // now can we read the files?
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_file_t file;
+        char name[16];
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            lfs_file_read(&lfs, &file, chunk, CHUNK) => CHUNK;
+
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                assert(chunk[k] == TEST_PRNG(&prng) & 0xff);
+            }
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+
+
+## backwards-compatibility tests ##
+
+# test we can mount in an old version
+[cases.test_compat_backward_mount]
+if = 'LFS_VERSION == LFSP_VERSION'
+code = '''
+    // create the new version
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // confirm the new mount works
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // now test the previous mount
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_mount(&lfsp, &cfgp) => 0;
+    lfsp_unmount(&lfsp) => 0;
+'''
+
+# test we can read dirs in an old version
+[cases.test_compat_backward_read_dirs]
+defines.COUNT = 5
+if = 'LFS_VERSION == LFSP_VERSION'
+code = '''
+    // create the new version
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // write COUNT dirs
+    lfs_mount(&lfs, cfg) => 0;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfs_mkdir(&lfs, name) => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+
+
+    // mount the new version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_mount(&lfsp, &cfgp) => 0;
+
+    // can we list the directories?
+    lfsp_dir_t dir;
+    lfsp_dir_open(&lfsp, &dir, "/") => 0;
+    struct lfsp_info info;
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_DIR);
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        assert(strcmp(info.name, name) == 0);
+    }
+
+    lfsp_dir_read(&lfsp, &dir, &info) => 0;
+    lfsp_dir_close(&lfsp, &dir) => 0;
+
+    lfsp_unmount(&lfsp) => 0;
+'''
+
+# test we can read files in an old version
+[cases.test_compat_backward_read_files]
+defines.COUNT = 5
+defines.SIZE = [4, 32, 512, 8192]
+defines.CHUNK = 4
+if = 'LFS_VERSION == LFSP_VERSION'
+code = '''
+    // create the new version
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // write COUNT files
+    lfs_mount(&lfs, cfg) => 0;
+    uint32_t prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfs_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfs_file_open(&lfs, &file, name,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+
+
+    // mount the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_mount(&lfsp, &cfgp) => 0;
+
+    // can we list the files?
+    lfsp_dir_t dir;
+    lfsp_dir_open(&lfsp, &dir, "/") => 0;
+    struct lfsp_info info;
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_REG);
+        char name[8];
+        sprintf(name, "file%03d", i);
+        assert(strcmp(info.name, name) == 0);
+        assert(info.size == SIZE);
+    }
+
+    lfsp_dir_read(&lfsp, &dir, &info) => 0;
+
+    // now can we read the files?
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfsp_file_open(&lfsp, &file, name, LFSP_O_RDONLY) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            lfsp_file_read(&lfsp, &file, chunk, CHUNK) => CHUNK;
+
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                assert(chunk[k] == TEST_PRNG(&prng) & 0xff);
+            }
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+    }
+
+    lfsp_unmount(&lfsp) => 0;
+'''
+
+# test we can read files in dirs in an old version
+[cases.test_compat_backward_read_files_in_dirs]
+defines.COUNT = 5
+defines.SIZE = [4, 32, 512, 8192]
+defines.CHUNK = 4
+if = 'LFS_VERSION == LFSP_VERSION'
+code = '''
+    // create the new version
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // write COUNT files+dirs
+    lfs_mount(&lfs, cfg) => 0;
+    uint32_t prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[16];
+        sprintf(name, "dir%03d", i);
+        lfs_mkdir(&lfs, name) => 0;
+
+        lfs_file_t file;
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfs_file_open(&lfs, &file, name,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfs_file_close(&lfs, &file) => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+
+
+    // mount the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_mount(&lfsp, &cfgp) => 0;
+
+    // can we list the directories?
+    lfsp_dir_t dir;
+    lfsp_dir_open(&lfsp, &dir, "/") => 0;
+    struct lfsp_info info;
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_DIR);
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        assert(strcmp(info.name, name) == 0);
+    }
+
+    lfsp_dir_read(&lfsp, &dir, &info) => 0;
+    lfsp_dir_close(&lfsp, &dir) => 0;
+
+    // can we list the files?
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfsp_dir_t dir;
+        lfsp_dir_open(&lfsp, &dir, name) => 0;
+        struct lfsp_info info;
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_DIR);
+        assert(strcmp(info.name, ".") == 0);
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_DIR);
+        assert(strcmp(info.name, "..") == 0);
+
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_REG);
+        sprintf(name, "file%03d", i);
+        assert(strcmp(info.name, name) == 0);
+        assert(info.size == SIZE);
+
+        lfsp_dir_read(&lfsp, &dir, &info) => 0;
+        lfsp_dir_close(&lfsp, &dir) => 0;
+    }
+
+    // now can we read the files?
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_file_t file;
+        char name[16];
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfsp_file_open(&lfsp, &file, name, LFSP_O_RDONLY) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            lfsp_file_read(&lfsp, &file, chunk, CHUNK) => CHUNK;
+
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                assert(chunk[k] == TEST_PRNG(&prng) & 0xff);
+            }
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+    }
+
+    lfsp_unmount(&lfsp) => 0;
+'''
+
+# test we can write dirs in an old version
+[cases.test_compat_backward_write_dirs]
+defines.COUNT = 10
+if = 'LFS_VERSION == LFSP_VERSION'
+code = '''
+    // create the new version
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // write COUNT/2 dirs
+    lfs_mount(&lfs, cfg) => 0;
+    for (lfs_size_t i = 0; i < COUNT/2; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfs_mkdir(&lfs, name) => 0;
+    }
+    lfs_unmount(&lfs) => 0;
+
+
+    // mount the previous version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_mount(&lfsp, &cfgp) => 0;
+
+    // write another COUNT/2 dirs
+    for (lfs_size_t i = COUNT/2; i < COUNT; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfsp_mkdir(&lfsp, name) => 0;
+    }
+
+    // can we list the directories?
+    lfsp_dir_t dir;
+    lfsp_dir_open(&lfsp, &dir, "/") => 0;
+    struct lfsp_info info;
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_DIR);
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        assert(strcmp(info.name, name) == 0);
+    }
+
+    lfsp_dir_read(&lfsp, &dir, &info) => 0;
+    lfsp_dir_close(&lfsp, &dir) => 0;
+
+    lfsp_unmount(&lfsp) => 0;
+'''
+
+# test we can write files in an old version
+[cases.test_compat_backward_write_files]
+defines.COUNT = 5
+defines.SIZE = [4, 32, 512, 8192]
+defines.CHUNK = 2
+if = 'LFS_VERSION == LFSP_VERSION'
+code = '''
+    // create the previous version
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // write half COUNT files
+    lfs_mount(&lfs, cfg) => 0;
+    uint32_t prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        // write half
+        lfs_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfs_file_open(&lfs, &file, name,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        for (lfs_size_t j = 0; j < SIZE/2; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfs_file_close(&lfs, &file) => 0;
+
+        // skip the other half but keep our prng reproducible
+        for (lfs_size_t j = SIZE/2; j < SIZE; j++) {
+            TEST_PRNG(&prng);
+        }
+    }
+    lfs_unmount(&lfs) => 0;
+
+
+    // mount the new version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_mount(&lfsp, &cfgp) => 0;
+
+    // write half COUNT files
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        // skip half but keep our prng reproducible
+        for (lfs_size_t j = 0; j < SIZE/2; j++) {
+            TEST_PRNG(&prng);
+        }
+
+        // write the other half
+        lfsp_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfsp_file_open(&lfsp, &file, name, LFSP_O_WRONLY) => 0;
+        lfsp_file_seek(&lfsp, &file, SIZE/2, LFSP_SEEK_SET) => SIZE/2;
+
+        for (lfs_size_t j = SIZE/2; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+    }
+
+    // can we list the files?
+    lfsp_dir_t dir;
+    lfsp_dir_open(&lfsp, &dir, "/") => 0;
+    struct lfsp_info info;
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_REG);
+        char name[8];
+        sprintf(name, "file%03d", i);
+        assert(strcmp(info.name, name) == 0);
+        assert(info.size == SIZE);
+    }
+
+    lfsp_dir_read(&lfsp, &dir, &info) => 0;
+
+    // now can we read the files?
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_file_t file;
+        char name[8];
+        sprintf(name, "file%03d", i);
+        lfsp_file_open(&lfsp, &file, name, LFSP_O_RDONLY) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            lfsp_file_read(&lfsp, &file, chunk, CHUNK) => CHUNK;
+
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                assert(chunk[k] == TEST_PRNG(&prng) & 0xff);
+            }
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+    }
+
+    lfsp_unmount(&lfsp) => 0;
+'''
+
+# test we can write files in dirs in an old version
+[cases.test_compat_backward_write_files_in_dirs]
+defines.COUNT = 5
+defines.SIZE = [4, 32, 512, 8192]
+defines.CHUNK = 2
+if = 'LFS_VERSION == LFSP_VERSION'
+code = '''
+    // create the previous version
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // write half COUNT files
+    lfs_mount(&lfs, cfg) => 0;
+    uint32_t prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[16];
+        sprintf(name, "dir%03d", i);
+        lfs_mkdir(&lfs, name) => 0;
+
+        // write half
+        lfs_file_t file;
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfs_file_open(&lfs, &file, name,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        for (lfs_size_t j = 0; j < SIZE/2; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfs_file_close(&lfs, &file) => 0;
+
+        // skip the other half but keep our prng reproducible
+        for (lfs_size_t j = SIZE/2; j < SIZE; j++) {
+            TEST_PRNG(&prng);
+        }
+    }
+    lfs_unmount(&lfs) => 0;
+
+
+    // mount the new version
+    struct lfsp_config cfgp;
+    memcpy(&cfgp, cfg, sizeof(cfgp));
+    lfsp_t lfsp;
+    lfsp_mount(&lfsp, &cfgp) => 0;
+
+    // write half COUNT files
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        // skip half but keep our prng reproducible
+        for (lfs_size_t j = 0; j < SIZE/2; j++) {
+            TEST_PRNG(&prng);
+        }
+
+        // write the other half
+        lfsp_file_t file;
+        char name[16];
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfsp_file_open(&lfsp, &file, name, LFSP_O_WRONLY) => 0;
+        lfsp_file_seek(&lfsp, &file, SIZE/2, LFSP_SEEK_SET) => SIZE/2;
+
+        for (lfs_size_t j = SIZE/2; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                chunk[k] = TEST_PRNG(&prng) & 0xff;
+            }
+
+            lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK;
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+    }
+
+    // can we list the directories?
+    lfsp_dir_t dir;
+    lfsp_dir_open(&lfsp, &dir, "/") => 0;
+    struct lfsp_info info;
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfsp_dir_read(&lfsp, &dir, &info) => 1;
+    assert(info.type == LFSP_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_DIR);
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        assert(strcmp(info.name, name) == 0);
+    }
+
+    lfsp_dir_read(&lfsp, &dir, &info) => 0;
+    lfsp_dir_close(&lfsp, &dir) => 0;
+
+    // can we list the files?
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        char name[8];
+        sprintf(name, "dir%03d", i);
+        lfsp_dir_t dir;
+        lfsp_dir_open(&lfsp, &dir, name) => 0;
+        struct lfsp_info info;
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_DIR);
+        assert(strcmp(info.name, ".") == 0);
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_DIR);
+        assert(strcmp(info.name, "..") == 0);
+
+        lfsp_dir_read(&lfsp, &dir, &info) => 1;
+        assert(info.type == LFSP_TYPE_REG);
+        sprintf(name, "file%03d", i);
+        assert(strcmp(info.name, name) == 0);
+        assert(info.size == SIZE);
+
+        lfsp_dir_read(&lfsp, &dir, &info) => 0;
+        lfsp_dir_close(&lfsp, &dir) => 0;
+    }
+
+    // now can we read the files?
+    prng = 42;
+    for (lfs_size_t i = 0; i < COUNT; i++) {
+        lfsp_file_t file;
+        char name[16];
+        sprintf(name, "dir%03d/file%03d", i, i);
+        lfsp_file_open(&lfsp, &file, name, LFSP_O_RDONLY) => 0;
+        for (lfs_size_t j = 0; j < SIZE; j += CHUNK) {
+            uint8_t chunk[CHUNK];
+            lfsp_file_read(&lfsp, &file, chunk, CHUNK) => CHUNK;
+
+            for (lfs_size_t k = 0; k < CHUNK; k++) {
+                assert(chunk[k] == TEST_PRNG(&prng) & 0xff);
+            }
+        }
+        lfsp_file_close(&lfsp, &file) => 0;
+    }
+
+    lfsp_unmount(&lfsp) => 0;
+'''
+
+
+
+## incompatiblity tests ##
+
+# test that we fail to mount after a major version bump
+[cases.test_compat_major_incompat]
+in = 'lfs.c'
+code = '''
+    // create a superblock
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // bump the major version
+    //
+    // note we're messing around with internals to do this! this
+    // is not a user API
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_mdir_t mdir;
+    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
+    lfs_superblock_t superblock = {
+        .version     = LFS_DISK_VERSION + 0x00010000,
+        .block_size  = lfs.cfg->block_size,
+        .block_count = lfs.cfg->block_count,
+        .name_max    = lfs.name_max,
+        .file_max    = lfs.file_max,
+        .attr_max    = lfs.attr_max,
+    };
+    lfs_superblock_tole32(&superblock);
+    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                &superblock})) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // mount should now fail
+    lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+'''
+
+# test that we fail to mount after a minor version bump
+[cases.test_compat_minor_incompat]
+in = 'lfs.c'
+code = '''
+    // create a superblock
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // bump the minor version
+    //
+    // note we're messing around with internals to do this! this
+    // is not a user API
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_mdir_t mdir;
+    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
+    lfs_superblock_t superblock = {
+        .version     = LFS_DISK_VERSION + 0x00000001,
+        .block_size  = lfs.cfg->block_size,
+        .block_count = lfs.cfg->block_count,
+        .name_max    = lfs.name_max,
+        .file_max    = lfs.file_max,
+        .attr_max    = lfs.attr_max,
+    };
+    lfs_superblock_tole32(&superblock);
+    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                &superblock})) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    // mount should now fail
+    lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+'''