From 2d4168776fa539c4496087017e7a94e2a37a93df Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 8 Jun 2024 22:37:52 -0500 Subject: [PATCH] Adopted upstream test_compact, readded lfsr_fs_stat test_compat has been very useful for testing compatibility on patch and minor releases. Though, in porting the tests, I've realized these are actually really flimsy w.r.t. API changes... lfsp_config notably relies on compatible struct layouts, which is _not_ guaranteed by littlefs's compatibility rules. For this reason I've restricted these tests to only run if LFS_VERSION doesn't change, though this may be worth reinvestigating in the future. test_compat on minor API releases would be quite valuable... lfsr_fs_stat is also not quite up to date with upstream yet. It's really just a small shim copying over static configs at the moment (except for name_limit/file_limit). This is because we're still missing most of what would actually be interesting here: variable block counts, minor versions, etc. And of course a minimal lfsr_fs_stat means minimal code changes: code stack before: 33642 2592 after: 33670 (+0.1%) 2592 (+0.0%) --- lfs.c | 18 +- lfs.h | 26 + tests/test_compat.toml | 1506 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1549 insertions(+), 1 deletion(-) create mode 100644 tests/test_compat.toml diff --git a/lfs.c b/lfs.c index bc29078b..578896db 100644 --- a/lfs.c +++ b/lfs.c @@ -8255,6 +8255,13 @@ static int lfsr_data_readwcompat(lfs_t *lfs, lfsr_data_t *data, return lfsr_data_readrcompat(lfs, data, wcompat); } +#define LFSR_DATA_OCOMPAT(_ocompat) LFSR_DATA_RCOMPAT(_ocompat) + +static int lfsr_data_readocompat(lfs_t *lfs, lfsr_data_t *data, + lfsr_ocompat_t *ocompat) { + return lfsr_data_readrcompat(lfs, data, ocompat); +} + // disk geometry // @@ -8908,7 +8915,16 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { } -/// Other filesystem traversal things /// +/// Other filesystem things /// + +int lfsr_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + fsinfo->disk_version = LFS_DISK_VERSION; + fsinfo->block_size = lfs->cfg->block_size; + fsinfo->block_count = lfs->cfg->block_count; + fsinfo->name_limit = lfs->name_limit; + fsinfo->file_limit = lfs->file_limit; + return 0; +} lfs_ssize_t lfsr_fs_size(lfs_t *lfs) { lfs_size_t count = 0; diff --git a/lfs.h b/lfs.h index f32137b1..551de1de 100644 --- a/lfs.h +++ b/lfs.h @@ -321,6 +321,26 @@ struct lfs_info { char name[LFS_NAME_MAX+1]; }; +// Filesystem info structure +struct lfs_fsinfo { + // On-disk version + uint32_t disk_version; + + // TODO should we add rcompat/wcompat flags here? + + // Size of a logical block in bytes. + lfs_size_t block_size; + + // Number of logical blocks in the filesystem. + lfs_size_t block_count; + + // Upper limit on the length of file names in bytes. + lfs_size_t name_limit; + + // Upper limit on the size of files in bytes. + lfs_size_t file_limit; +}; + //// Custom attribute structure, used to describe custom attributes //// committed atomically during file writes. //struct lfs_attr { @@ -939,6 +959,12 @@ int lfsr_dir_rewind(lfs_t *lfs, lfsr_dir_t *dir); /// Filesystem-level filesystem operations +// Find on-disk info about the filesystem +// +// Fills out the fsinfo structure based on the filesystem found on-disk. +// Returns a negative error code on failure. +int lfsr_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); + // Finds the current size of the filesystem // // Note: Result is best effort. If files share COW structures, the returned diff --git a/tests/test_compat.toml b/tests/test_compat.toml new file mode 100644 index 00000000..2e5db317 --- /dev/null +++ b/tests/test_compat.toml @@ -0,0 +1,1506 @@ +# Test compatibility between different littlefs versions +after = [ + 'test_dirs', + 'test_files', + 'test_fwrite', + 'test_forphans', +] + +# Note, these tests are a bit special. They can run as-is to test various +# compatbility corner-cases, but if you link a previous version of littlefs +# as "lfsp" and define LFSP, they will also test migrating from lfsp -> lfs. +# +# If LFSP is not defined, these tests will alias lfsp = lfs so things will +# still compile and run, but the value of many of these tests is to test +# against a previous version linked in with the help of +# scripts/changeprefix.py +# +# Though we are also limited by API changes... so by default these tests +# won't run if LFS_VERSION changes. This can be bypassed with the test +# runner's -a/--all flag. +# + +# 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_DISK_VERSION LFS_DISK_VERSION +#define LFSP_DISK_VERSION_MAJOR LFS_DISK_VERSION_MAJOR +#define LFSP_DISK_VERSION_MINOR LFS_DISK_VERSION_MINOR +#define lfsp_t lfs_t +#define lfsp_config lfs_config +#define LFSP_ERR_NOENT LFS_ERR_NOENT +#define lfsp_format lfsr_format +#define lfsp_mount lfsr_mount +#define lfsp_unmount lfsr_unmount +#define lfsp_fsinfo lfs_fsinfo +#define lfsp_fs_stat lfsr_fs_stat +#define lfsp_dir_t lfsr_dir_t +#define lfsp_info lfs_info +#define LFSP_TYPE_REG LFS_TYPE_REG +#define LFSP_TYPE_DIR LFS_TYPE_DIR +#define lfsp_mkdir lfsr_mkdir +#define lfsp_dir_open lfsr_dir_open +#define lfsp_dir_read lfsr_dir_read +#define lfsp_dir_close lfsr_dir_close +#define lfsp_file_t lfsr_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 lfsr_file_open +#define lfsp_file_write lfsr_file_write +#define lfsp_file_read lfsr_file_read +#define lfsp_file_seek lfsr_file_seek +#define lfsp_file_close lfsr_file_close +#endif +''' + + +## forward-compatibility tests ## + +# test we can mount in a new version +[cases.test_compat_forward_mount] +if = [ + 'LFS_VERSION == LFSP_VERSION', + 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR', +] +code = ''' + // create the previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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 mount with new version + lfs_t lfs; + lfsr_mount(&lfs, CFG) => 0; + + // we should be able to read the version using lfsr_fs_stat + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + + lfsr_unmount(&lfs) => 0; +''' + +# test we can read dirs in a new version +[cases.test_compat_forward_read_dirs] +defines.COUNT = 5 +if = [ + 'LFS_VERSION == LFSP_VERSION', + 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR', +] +code = ''' + // create the previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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; + + ////// + + // now mount with new version + lfs_t lfs; + lfsr_mount(&lfs, CFG) => 0; + + // we should be able to read the version using lfsr_fs_stat + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + + // can we list the directories? + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + char name[8]; + sprintf(name, "dir%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_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 == LFSP_VERSION', + 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR', +] +code = ''' + // create the previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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; + + ////// + + // now mount with new version + lfs_t lfs; + lfsr_mount(&lfs, CFG) => 0; + + // we should be able to read the version using lfsr_fs_stat + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + + // can we list the files? + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + char name[8]; + sprintf(name, "file%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // now can we read the files? + prng = 42; + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_file_t file; + char name[8]; + sprintf(name, "file%03d", i); + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { + uint8_t chunk[CHUNK]; + lfsr_file_read(&lfs, &file, chunk, CHUNK) => CHUNK; + + for (lfs_size_t k = 0; k < CHUNK; k++) { + assert(chunk[k] == (TEST_PRNG(&prng) & 0xff)); + } + } + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_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 == LFSP_VERSION', + 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR', +] +code = ''' + // create the previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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; + + ////// + + // now mount with new version + lfs_t lfs; + lfsr_mount(&lfs, CFG) => 0; + + // we should be able to read the version using lfsr_fs_stat + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + + // can we list the directories? + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + char name[8]; + sprintf(name, "dir%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_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); + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, name) => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + lfsr_dir_read(&lfs, &dir, &info) => 0; + sprintf(name, "file%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + + // now can we read the files? + prng = 42; + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_file_t file; + char name[16]; + sprintf(name, "dir%03d/file%03d", i, i); + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { + uint8_t chunk[CHUNK]; + lfsr_file_read(&lfs, &file, chunk, CHUNK) => CHUNK; + + for (lfs_size_t k = 0; k < CHUNK; k++) { + assert(chunk[k] == (TEST_PRNG(&prng) & 0xff)); + } + } + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +# test we can write dirs in a new version +[cases.test_compat_forward_write_dirs] +defines.COUNT = 10 +if = [ + 'LFS_VERSION == LFSP_VERSION', + 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR', +] +code = ''' + // create the previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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; + + ////// + + // now mount with new version + lfs_t lfs; + lfsr_mount(&lfs, CFG) => 0; + + // we should be able to read the version using lfsr_fs_stat + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + + // write another COUNT/2 dirs + for (lfs_size_t i = COUNT/2; i < COUNT; i++) { + char name[8]; + sprintf(name, "dir%03d", i); + lfsr_mkdir(&lfs, name) => 0; + } + + // can we list the directories? + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + char name[8]; + sprintf(name, "dir%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_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 == LFSP_VERSION', + 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR', +] +code = ''' + // create the previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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; + + ////// + + // now mount with new version + lfs_t lfs; + lfsr_mount(&lfs, CFG) => 0; + + // we should be able to read the version using lfsr_fs_stat + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + + // 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 + lfsr_file_t file; + char name[8]; + sprintf(name, "file%03d", i); + lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY) => 0; + lfsr_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; + } + + lfsr_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; + } + lfsr_file_close(&lfs, &file) => 0; + } + + // can we list the files? + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + char name[8]; + sprintf(name, "file%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // now can we read the files? + prng = 42; + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_file_t file; + char name[8]; + sprintf(name, "file%03d", i); + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { + uint8_t chunk[CHUNK]; + lfsr_file_read(&lfs, &file, chunk, CHUNK) => CHUNK; + + for (lfs_size_t k = 0; k < CHUNK; k++) { + assert(chunk[k] == (TEST_PRNG(&prng) & 0xff)); + } + } + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_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 == LFSP_VERSION', + 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR', +] +code = ''' + // create the previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + memcpy(&cfgp, CFG, sizeof(cfgp)); + lfsp_t lfsp; + lfsp_format(&lfsp, &cfgp) => 0; + + ////// + + // now mount with new version + 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; + lfsr_mount(&lfs, CFG) => 0; + + // we should be able to read the version using lfsr_fs_stat + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + + // 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 + lfsr_file_t file; + char name[16]; + sprintf(name, "dir%03d/file%03d", i, i); + lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY) => 0; + lfsr_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; + } + + lfsr_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; + } + lfsr_file_close(&lfs, &file) => 0; + } + + // can we list the directories? + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + char name[8]; + sprintf(name, "dir%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_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); + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, name) => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + lfsr_dir_read(&lfs, &dir, &info) => 0; + sprintf(name, "file%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + + // now can we read the files? + prng = 42; + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_file_t file; + char name[16]; + sprintf(name, "dir%03d/file%03d", i, i); + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { + uint8_t chunk[CHUNK]; + lfsr_file_read(&lfs, &file, chunk, CHUNK) => CHUNK; + + for (lfs_size_t k = 0; k < CHUNK; k++) { + assert(chunk[k] == (TEST_PRNG(&prng) & 0xff)); + } + } + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + + + +## backwards-compatibility tests ## + +# test we can mount in an old version +[cases.test_compat_backward_mount] +if = [ + 'LFS_VERSION == LFSP_VERSION', + 'LFS_DISK_VERSION == LFSP_DISK_VERSION', +] +code = ''' + // create the new version + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // confirm the new mount works + lfsr_mount(&lfs, CFG) => 0; + lfsr_unmount(&lfs) => 0; + + ////// + + // now mount with previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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', + 'LFS_DISK_VERSION == LFSP_DISK_VERSION', +] +code = ''' + // create the new version + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // write COUNT dirs + lfsr_mount(&lfs, CFG) => 0; + for (lfs_size_t i = 0; i < COUNT; i++) { + char name[8]; + sprintf(name, "dir%03d", i); + lfsr_mkdir(&lfs, name) => 0; + } + lfsr_unmount(&lfs) => 0; + + ////// + + // now mount with previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + lfsp_dir_read(&lfsp, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsp_dir_read(&lfsp, &dir, &info) => 0; + char name[8]; + sprintf(name, "dir%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + } + + lfsp_dir_read(&lfsp, &dir, &info) => LFSP_ERR_NOENT; + 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', + 'LFS_DISK_VERSION == LFSP_DISK_VERSION', +] +code = ''' + // create the new version + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // write COUNT files + lfsr_mount(&lfs, CFG) => 0; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsr_file_t file; + char name[8]; + sprintf(name, "file%03d", i); + lfsr_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; + } + + lfsr_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; + } + lfsr_file_close(&lfs, &file) => 0; + } + lfsr_unmount(&lfs) => 0; + + ////// + + // now mount with previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + lfsp_dir_read(&lfsp, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsp_dir_read(&lfsp, &dir, &info) => 0; + char name[8]; + sprintf(name, "file%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFSP_TYPE_REG); + assert(info.size == SIZE); + } + + lfsp_dir_read(&lfsp, &dir, &info) => LFSP_ERR_NOENT; + 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[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', + 'LFS_DISK_VERSION == LFSP_DISK_VERSION', +] +code = ''' + // create the new version + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // write COUNT files+dirs + lfsr_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); + lfsr_mkdir(&lfs, name) => 0; + + lfsr_file_t file; + sprintf(name, "dir%03d/file%03d", i, i); + lfsr_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; + } + + lfsr_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; + } + lfsr_file_close(&lfs, &file) => 0; + } + lfsr_unmount(&lfs) => 0; + + ////// + + // now mount with previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + lfsp_dir_read(&lfsp, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsp_dir_read(&lfsp, &dir, &info) => 0; + char name[8]; + sprintf(name, "dir%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + } + + lfsp_dir_read(&lfsp, &dir, &info) => LFSP_ERR_NOENT; + 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) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + lfsp_dir_read(&lfsp, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + + lfsp_dir_read(&lfsp, &dir, &info) => 0; + sprintf(name, "file%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFSP_TYPE_REG); + assert(info.size == SIZE); + + lfsp_dir_read(&lfsp, &dir, &info) => LFSP_ERR_NOENT; + 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', + 'LFS_DISK_VERSION == LFSP_DISK_VERSION', +] +code = ''' + // create the new version + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // write COUNT/2 dirs + lfsr_mount(&lfs, CFG) => 0; + for (lfs_size_t i = 0; i < COUNT/2; i++) { + char name[8]; + sprintf(name, "dir%03d", i); + lfsr_mkdir(&lfs, name) => 0; + } + lfsr_unmount(&lfs) => 0; + + ////// + + // now mount with previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + lfsp_dir_read(&lfsp, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsp_dir_read(&lfsp, &dir, &info) => 0; + char name[8]; + sprintf(name, "dir%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + } + + lfsp_dir_read(&lfsp, &dir, &info) => LFSP_ERR_NOENT; + 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', + 'LFS_DISK_VERSION == LFSP_DISK_VERSION', +] +code = ''' + // create the previous version + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // write half COUNT files + lfsr_mount(&lfs, CFG) => 0; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < COUNT; i++) { + // write half + lfsr_file_t file; + char name[8]; + sprintf(name, "file%03d", i); + lfsr_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; + } + + lfsr_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; + } + lfsr_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); + } + } + lfsr_unmount(&lfs) => 0; + + ////// + + // now mount with previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + lfsp_dir_read(&lfsp, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsp_dir_read(&lfsp, &dir, &info) => 0; + char name[8]; + sprintf(name, "file%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFSP_TYPE_REG); + assert(info.size == SIZE); + } + + lfsp_dir_read(&lfsp, &dir, &info) => LFSP_ERR_NOENT; + 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[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', + 'LFS_DISK_VERSION == LFSP_DISK_VERSION', +] +code = ''' + // create the previous version + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // write half COUNT files + lfsr_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); + lfsr_mkdir(&lfs, name) => 0; + + // write half + lfsr_file_t file; + sprintf(name, "dir%03d/file%03d", i, i); + lfsr_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; + } + + lfsr_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; + } + lfsr_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); + } + } + lfsr_unmount(&lfs) => 0; + + ////// + + // now mount with previous version + struct lfsp_config cfgp; + assert(sizeof(struct lfsp_config) == sizeof(struct lfs_config)); + 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) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + lfsp_dir_read(&lfsp, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + + for (lfs_size_t i = 0; i < COUNT; i++) { + lfsp_dir_read(&lfsp, &dir, &info) => 0; + char name[8]; + sprintf(name, "dir%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + } + + lfsp_dir_read(&lfsp, &dir, &info) => LFSP_ERR_NOENT; + 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) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + lfsp_dir_read(&lfsp, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFSP_TYPE_DIR); + assert(info.size == 0); + + lfsp_dir_read(&lfsp, &dir, &info) => 0; + sprintf(name, "file%03d", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFSP_TYPE_REG); + assert(info.size == SIZE); + + lfsp_dir_read(&lfsp, &dir, &info) => LFSP_ERR_NOENT; + 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; + lfsr_format(&lfs, CFG) => 0; + + // bump the major version + // + // note we're messing around with internals to do this! this + // is not a user API + lfsr_mount(&lfs, CFG) => 0; + lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_VERSION, 0, + LFSR_DATA_BUF(((const uint8_t[2]){ + LFS_DISK_VERSION_MAJOR+1, + 0}), 2)))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount should now fail + lfsr_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; + lfsr_format(&lfs, CFG) => 0; + + // bump the minor version + // + // note we're messing around with internals to do this! this + // is not a user API + lfsr_mount(&lfs, CFG) => 0; + lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_VERSION, 0, + LFSR_DATA_BUF(((const uint8_t[2]){ + LFS_DISK_VERSION_MAJOR, + LFS_DISK_VERSION_MINOR+1}), 2)))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount should now fail + lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL; +''' + +# test that we fail to mount after incompatible rcompat flags +[cases.test_compat_rcompat_incompat] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // set the nonstandard rcompat flag, this will always be incompatible + // with standard littlefs + // + // note we're messing around with internals to do this! this + // is not a user API + lfsr_mount(&lfs, CFG) => 0; + lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_RCOMPAT, 0, + LFSR_DATA_RCOMPAT( + LFSR_RCOMPAT_COMPAT + | LFSR_RCOMPAT_NONSTANDARD)))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount should now fail + lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL; +''' + +# test that we fail to mount after incompatible wcompat flags +[cases.test_compat_wcompat_incompat] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // set the nonstandard rcompat flag, this will always be incompatible + // with standard littlefs + // + // note we're messing around with internals to do this! this + // is not a user API + lfsr_mount(&lfs, CFG) => 0; + lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_WCOMPAT, 0, + LFSR_DATA_WCOMPAT( + LFSR_WCOMPAT_COMPAT + | LFSR_WCOMPAT_NONSTANDARD)))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount should now fail + lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL; +''' + +# test that an incompatible ocompat flag is a noop +[cases.test_compat_ocompat_incompat] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // set the nonstandard ocompat flag, this will always be incompatible + // with standard littlefs + // + // note we're messing around with internals to do this! this + // is not a user API + lfsr_mount(&lfs, CFG) => 0; + lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_OCOMPAT, 0, + LFSR_DATA_OCOMPAT( + LFSR_OCOMPAT_COMPAT + | LFSR_OCOMPAT_NONSTANDARD)))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount should _not_ fail, ocompat should always be ignored + lfsr_mount(&lfs, CFG) => 0; + lfsr_unmount(&lfs) => 0; +'''