From 582dc5f1b27bfbceae58ed4a01a991830163c4c2 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 25 Sep 2023 11:58:27 -0500 Subject: [PATCH] Added some tests, quick seek impl, fixed bugs Turns out it's hard to test file holes without seek. It's interesting to note most of seek's buffer flush work actually occurs lazily in lfsr_file_write, so lfsr_file_seek turns out to be a relatively simple function. --- lfs.c | 61 +++-- lfs.h | 4 + tests/test_ftree.toml | 551 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 597 insertions(+), 19 deletions(-) diff --git a/lfs.c b/lfs.c index d9970d60..fc2f78b1 100644 --- a/lfs.c +++ b/lfs.c @@ -8487,18 +8487,13 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { // remove any data we're overwriting, note we need to account for // left_sibling changes - *attrs_++ = LFSR_ATTR( - lfs_min32( - file->buffer_pos + file->buffer_size - + right_weight - left_overlap, - file->inlined.u.rbyd.weight)-1, - RM, - -(lfs_min32( - file->buffer_pos + file->buffer_size - + right_weight - left_overlap, - file->inlined.u.rbyd.weight) - - file->buffer_pos), - NULL); + lfs_off_t rm_size = lfs_min32( + file->buffer_pos + file->buffer_size + + right_weight - left_overlap, + file->inlined.u.rbyd.weight - left_overlap) + - file->buffer_pos; + *attrs_++ = LFSR_ATTR(file->buffer_pos + rm_size - 1, + RM, -rm_size, NULL); if (lfsr_data_size(&right_data) == 0) { // append our buffer with any remaining weight @@ -8591,11 +8586,6 @@ failed:; return err; } -int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) { - // TODO - return 0; -} - int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { if (lfsr_file_iserrored(file->flags)) { // it's not safe to do anything if our file errored @@ -8682,6 +8672,43 @@ failed:; return err; } +lfs_soff_t lfsr_file_seek(lfs_t *lfs, lfsr_file_t *file, + lfs_soff_t off, uint8_t whence) { + // TODO check for out-of-range? + + // figure out our new file position + lfs_off_t pos_; + if (whence == LFS_SEEK_SET) { + pos_ = off; + } else if (whence == LFS_SEEK_CUR) { + pos_ = file->pos + off; + } else if (whence == LFS_SEEK_END) { + pos_ = file->size + off; + } else { + LFS_UNREACHABLE(); + } + + // out of range? + if (pos_ > lfs->size_limit) { + return LFS_ERR_INVAL; + } + + // update file position + file->pos = pos_; + return pos_; +} + +lfs_soff_t lfsr_file_tell(lfs_t *lfs, lfsr_file_t *file) { + (void)lfs; + return file->pos; +} + +lfs_soff_t lfsr_file_rewind(lfs_t *lfs, lfsr_file_t *file) { + (void)lfs; + file->pos = 0; + return 0; +} + lfs_soff_t lfsr_file_size(lfs_t *lfs, lfsr_file_t *file) { (void)lfs; return file->size; diff --git a/lfs.h b/lfs.h index 4b239a75..0fc0f3e1 100644 --- a/lfs.h +++ b/lfs.h @@ -781,6 +781,8 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, // Returns the new position of the file, or a negative error code on failure. lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t off, int whence); +lfs_soff_t lfsr_file_seek(lfs_t *lfs, lfsr_file_t *file, + lfs_soff_t off, uint8_t whence); #ifndef LFS_READONLY // Truncates the size of the file to the specified size @@ -794,12 +796,14 @@ int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) // Returns the position of the file, or a negative error code on failure. lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); +lfs_soff_t lfsr_file_tell(lfs_t *lfs, lfsr_file_t *file); // Change the position of the file to the beginning of the file // // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) // Returns a negative error code on failure. int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); +int lfsr_file_rewind(lfs_t *lfs, lfsr_file_t *file); // Return the size of the file // diff --git a/tests/test_ftree.toml b/tests/test_ftree.toml index f25cb444..864aa871 100644 --- a/tests/test_ftree.toml +++ b/tests/test_ftree.toml @@ -510,7 +510,7 @@ code = ''' # write files incrementally [cases.test_ftree_incr] defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] -defines.CHUNK = [4, 1] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] defines.SYNC = [false, true] defines.REMOUNT = [false, true] reentrant = true @@ -547,7 +547,7 @@ code = ''' lfsr_mount(&lfs, CFG) => 0; // note the switch to append here lfsr_file_open(&lfs, &file, "hello", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + LFS_O_WRONLY | LFS_O_APPEND) => 0; } } lfsr_file_close(&lfs, &file) => 0; @@ -595,6 +595,553 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# overwrite files +[cases.test_ftree_overwrite] +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +# bit 0 => first chunk +# bit 1 => middle chunk +# bit 2 => last chunk +defines.MASK = [0, 1, 2, 3, 4, 5, 6, 7] +# 0 => in-order +# 1 => reversed +defines.ORDER = [0, 1] +defines.SYNC = [false, true] +defines.REMOUNT = [false, true] +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 a file, truncating in case of powerloss + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + // simulate our file in ram + uint8_t sim[8192]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // write first chunk? + if (MASK & 1) { + if (ORDER == 0) { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK; + } else { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK; + lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK; + } + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // write second chunk? + if (MASK & 2) { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[SIZE/2-CHUNK/2+i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, SIZE/2 - CHUNK/2, LFS_SEEK_SET) + => SIZE/2 - CHUNK/2; + lfsr_file_write(&lfs, &file, &sim[SIZE/2-CHUNK/2], CHUNK) => CHUNK; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // write third chunk? + if (MASK & 4) { + if (ORDER == 0) { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK; + lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK; + } else { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK; + } + } + + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 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; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => SIZE; + // try reading + uint8_t rbuf[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE; + // does our file match our simulation? + assert(memcmp(rbuf, sim, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# similar to overwrite files, but without underlying data +[cases.test_ftree_holes] +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +# bit 0 => first chunk +# bit 1 => middle chunk +# bit 2 => last chunk +defines.MASK = [4, 5, 6, 7] # TODO 0 1 2 3 ? need truncate? +# 0 => in-order +# 1 => reversed +defines.ORDER = [0] # TODO 1? +defines.SYNC = [false, true] +defines.REMOUNT = [false, true] +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 a file, truncating in case of powerloss + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + // simulate our file in ram + uint8_t sim[8192]; + uint32_t prng = 42; + memset(sim, 0, SIZE); + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // write first chunk? + if (MASK & 1) { + if (ORDER == 0) { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK; + } else { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK; + lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK; + } + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // write second chunk? + if (MASK & 2) { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[SIZE/2-CHUNK/2+i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, SIZE/2 - CHUNK/2, LFS_SEEK_SET) + => SIZE/2 - CHUNK/2; + lfsr_file_write(&lfs, &file, &sim[SIZE/2-CHUNK/2], CHUNK) => CHUNK; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // write third chunk? + if (MASK & 4) { + if (ORDER == 0) { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK; + lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK; + } else { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK; + } + } + + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 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; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => SIZE; + // try reading + uint8_t rbuf[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE; + // does our file match our simulation? + assert(memcmp(rbuf, sim, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# writing any data structure backwards always reveals issues +[cases.test_ftree_reversed_overwrite] +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +defines.SYNC = [false, true] +defines.REMOUNT = [false, true] +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 a file, truncating in case of powerloss + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + // simulate our file in ram + uint8_t sim[8192]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // write to file incrementally and backwards + for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { + for (lfs_size_t j = 0; j < CHUNK; j++) { + sim[SIZE-i-CHUNK+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_seek(&lfs, &file, SIZE-i-CHUNK, LFS_SEEK_SET) => SIZE-i-CHUNK; + lfsr_file_write(&lfs, &file, &sim[SIZE-i-CHUNK], CHUNK) => CHUNK; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + } + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 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; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => SIZE; + // try reading + uint8_t rbuf[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE; + // does our file match our simulation? + assert(memcmp(rbuf, sim, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# writing any data structure backwards always reveals issues +[cases.test_ftree_reversed_holes] +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +defines.SYNC = [false, true] +defines.REMOUNT = [false, true] +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 a file, truncating in case of powerloss + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + // simulate our file in ram + uint8_t sim[8192]; + uint32_t prng = 42; + memset(sim, 0, SIZE); + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // write to file incrementally and backwards + for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { + for (lfs_size_t j = 0; j < CHUNK; j++) { + sim[SIZE-i-CHUNK+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_seek(&lfs, &file, SIZE-i-CHUNK, LFS_SEEK_SET) => SIZE-i-CHUNK; + lfsr_file_write(&lfs, &file, &sim[SIZE-i-CHUNK], CHUNK) => CHUNK; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + } + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 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; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => SIZE; + // try reading + uint8_t rbuf[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE; + // does our file match our simulation? + assert(memcmp(rbuf, sim, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +'''