From 6261bafed26b8b911fe2e3b7065b062a50bef6ec Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 1 Dec 2023 15:58:44 -0600 Subject: [PATCH] Added more file tests with multiple files, fixed bugs Fortunately these operations are heavily tested in test_dirs. The only difference with files is the possibility for shrubs to need to be copied. Bugs fixed: - It's counterintuitive, but lfsr_rbyd_appendcompactattr _can_ error with LFS_ERR_RANGE when we are copying a shrub. This can happen if the underlying mdir needs compaction itself. - It's possible to null-trunk bshrubs to appear in our filesystem traversal. Null-trunk bshrubs don't usually appear in any stable state, but they are created by lfsr_bshrub_alloc and lfsr_btree_commit to represent new, yet-uncommitted shrubs. This gets a bit tricky because we also use null-trunks to indicate if lfsr_btree_traversal has traversed the root. We can't rely on bid >= weight for this because zero-weight btrees are allowed. The solution here, though maybe temporary (famous last words), is to treat null-trunk btrees as not having a root. Which isn't really true, but null-trunk btree roots only exist between allocator checkpoints, so they are allowed to be unreachable. We really need more asserts that this is the case though... At least added an assert that we never commit/read null trunks on disk. --- lfs.c | 12 +- tests/test_files.toml | 1072 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1069 insertions(+), 15 deletions(-) diff --git a/lfs.c b/lfs.c index 9b01076c..aca58db5 100644 --- a/lfs.c +++ b/lfs.c @@ -1862,6 +1862,8 @@ static int lfsr_data_readgrm(lfs_t *lfs, lfsr_data_t *data, static lfsr_data_t lfsr_data_fromtrunk(lfs_size_t trunk, lfsr_rid_t weight, uint8_t buffer[static LFSR_TRUNK_DSIZE]) { + // shrub trunks should never be null + LFS_ASSERT(trunk != 0); lfs_ssize_t d = 0; // just write the trunk and weight, the rest of the rbyd is contextual @@ -1890,6 +1892,8 @@ static int lfsr_data_readtrunk(lfs_t *lfs, lfsr_data_t *data, return err; } + // shrub trunks should never be null + LFS_ASSERT(*trunk != 0); return 0; } @@ -3267,7 +3271,6 @@ static int lfsr_rbyd_appendcompactrbyd(lfs_t *lfs, lfsr_rbyd_t *rbyd_, err = lfsr_rbyd_appendcompactattr(lfs, rbyd_, ((shrub) ? LFSR_TAG_SHRUB : 0) | tag, weight, data); if (err) { - LFS_ASSERT(err != LFS_ERR_RANGE); return err; } } @@ -4608,14 +4611,13 @@ typedef struct lfsr_binfo { static int lfsr_btree_traverse(lfs_t *lfs, const lfsr_btree_t *btree, lfsr_btraversal_t *btraversal, lfsr_binfo_t *binfo) { - // this shouldn't happen - LFS_ASSERT(btree->trunk != 0); - while (true) { // in range? if (btraversal->bid >= (lfsr_bid_t)btree->weight // make sure we traverse the root even if weight=0 - && btraversal->branch.trunk != 0) { + && (btraversal->branch.trunk != 0 + // unless we don't even have a root yet + || btree->trunk == 0)) { return LFS_ERR_NOENT; } diff --git a/tests/test_files.toml b/tests/test_files.toml index 741ae458..010c2d2f 100644 --- a/tests/test_files.toml +++ b/tests/test_files.toml @@ -644,6 +644,229 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# test creating multiple files +[cases.test_files_many] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +if = [ + '(SIZE*N)/BLOCK_SIZE <= 32', + # limit powerloss testing due to time + # TODO can this be increased after optimizing file writes? + '!TEST_PLS || (SIZE*N) <= 1*BLOCK_SIZE', +] +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%04d", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + // TODO remove this eventually? + // + // file may exist from a previous run, but we need to check + // for pesky zero-sized files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_ssize_t size = lfsr_file_size(&lfs, &file); + assert(size == 0 || size == SIZE); + if (size == 0) { + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + } + lfsr_file_close(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our writes worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%04d", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +# fuzz test file creation +[cases.test_files_fuzz] +defines.N = [1, 2, 4, 8, 16, 32, 64] +# do more ops than files to encourage file rewrites +defines.DENSITY = 2 +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +defines.SEED = 'range(10)' +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } + + // create a file here + char name[256]; + sprintf(name, "amethyst%04d", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + 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; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + + # test removing files of various sizes # # to be honest, this doesn't really test much and is just included @@ -668,7 +891,7 @@ code = ''' // create a file lfsr_file_t file; - lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfsr_file_open(&lfs, &file, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { @@ -684,7 +907,7 @@ code = ''' } // remove our file - lfsr_remove(&lfs, "hello") => 0; + lfsr_remove(&lfs, "amethyst") => 0; // remount? if (REMOUNT) { @@ -694,7 +917,7 @@ code = ''' // check our file with stat struct lfs_info info; - lfsr_stat(&lfs, "hello", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "amethyst", &info) => LFS_ERR_NOENT; // and with dir read lfsr_dir_t dir; @@ -711,6 +934,307 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# test creating and deleting multiple files +[cases.test_files_rm_many] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.REMAINING = [4, 1, 0] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +if = [ + 'N > REMAINING', + '(SIZE*N)/BLOCK_SIZE <= 32', + # limit powerloss testing due to time + # TODO can this be increased after optimizing file writes? + '!TEST_PLS || ((SIZE*N) <= BLOCK_SIZE/4 && N <= 16)', +] +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%04d", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + // TODO remove this eventually? + // + // file may exist from a previous run, but we need to check + // for pesky zero-sized files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_ssize_t size = lfsr_file_size(&lfs, &file); + assert(size == 0 || size == SIZE); + if (size == 0) { + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + } + lfsr_file_close(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our writes worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%04d", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // now remove some number of files + for (lfs_size_t i = 0; i < N-REMAINING; i++) { + char name[256]; + sprintf(name, "amethyst%04d", i); + lfsr_remove(&lfs, name) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // check that our removes worked + // + // note we need to keep the prng in sync + // + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%04d", i); + + // keep prng in sync + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + if (i < N-REMAINING) { + // check with stat + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => LFS_ERR_NOENT; + + // and try to open + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_NOENT; + } else { + // check with stat + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +# fuzz test file creation and deletion +[cases.test_files_rm_fuzz] +defines.N = [1, 2, 4, 8, 16, 32, 64] +# do more ops than files to encourage file rewrites +defines.DENSITY = 2 +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +defines.SEED = 'range(10)' +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 2; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } + + // create a file here + char name[256]; + sprintf(name, "amethyst%04d", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // delete this file + char name[256]; + sprintf(name, "amethyst%04d", x); + lfsr_remove(&lfs, name) => 0; + } + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + 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; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + # test renaming files of various sizes [cases.test_files_mv] defines.SIZE = [ @@ -1336,20 +1860,548 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# test renaming multiple files +[cases.test_files_mv_many] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +if = [ + '(SIZE*N)/BLOCK_SIZE <= 32', + # limit powerloss testing due to time + # TODO can this be increased after optimizing file writes? + '!TEST_PLS || ((SIZE*N) <= BLOCK_SIZE/4 && N <= 16)', +] +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%04d", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + // TODO remove this eventually? + // + // file may exist from a previous run, but we need to check + // for pesky zero-sized files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_ssize_t size = lfsr_file_size(&lfs, &file); + assert(size == 0 || size == SIZE); + if (size == 0) { + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + } + lfsr_file_close(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our writes worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%04d", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // now rename our files + for (lfs_size_t i = 0; i < N; i++) { + char old_name[256]; + sprintf(old_name, "amethyst%04d", i); + char new_name[256]; + sprintf(new_name, "basalt%04d", i); + + lfsr_rename(&lfs, old_name, new_name) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // check that our renames worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%04d", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => LFS_ERR_NOENT; + sprintf(name, "basalt%04d", i); + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +# fuzz test file creation and rename +[cases.test_files_mv_fuzz] +defines.N = [1, 2, 4, 8, 16, 32, 64] +# do more ops than files to encourage file rewrites +defines.DENSITY = 2 +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +defines.SEED = 'range(10)' +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 2; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } + + // create a file here + char name[256]; + sprintf(name, "amethyst%04d", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%04d", x); + char new_name[256]; + sprintf(new_name, "amethyst%04d", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + 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; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + +# fuzz test file creation/deletion/rename +[cases.test_files_mvrm_fuzz] +defines.N = [1, 2, 4, 8, 16, 32, 64] +# do more ops than files to encourage file rewrites +defines.DENSITY = 2 +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +defines.SEED = 'range(10)' +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } + + // create a file here + char name[256]; + sprintf(name, "amethyst%04d", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // delete this file + char name[256]; + sprintf(name, "amethyst%04d", x); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%04d", x); + char new_name[256]; + sprintf(new_name, "amethyst%04d", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + 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; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%04d", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + # TODO -# [cases.test_files_rm] -# [cases.test_files_mv] -# [cases.test_files_mvrm] -# [cases.test_files_rmed] -# [cases.test_files_mved] -# [cases.test_files_mvrmed] # [cases.test_files_multi_readers] # [cases.test_files_multi_readers_one_writer] # [cases.test_files_multi_writers] # [cases.test_files_multi_readers_multi_writers] -# [cases.test_files_many] # [cases.test_files_interleaved] # [cases.test_files_interleaved_fuzz] # [cases.test_files_interleaved_fuzz_fuzz]