From 116332d3f7e33e2d4ada3b4896024b1d44567593 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 20 Apr 2023 02:13:58 -0500 Subject: [PATCH] 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. --- Makefile | 27 +- tests/test_compat.toml | 1278 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1294 insertions(+), 11 deletions(-) create mode 100644 tests/test_compat.toml diff --git a/Makefile b/Makefile index 50167156..24865e5e 100644 --- a/Makefile +++ b/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 $@ diff --git a/tests/test_compat.toml b/tests/test_compat.toml new file mode 100644 index 00000000..7af487f5 --- /dev/null +++ b/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; +'''