Implemented and tested lfsr_file_truncate

Not much to say here. We need to modify trees a bit, but at least it's
relatively straightforward.
This commit is contained in:
Christopher Haster
2023-09-29 00:58:29 -05:00
parent 981e64f524
commit 5adc1f54b7
3 changed files with 568 additions and 87 deletions
+395 -73
View File
@@ -644,15 +644,17 @@ code = '''
}
// write first chunk?
if (MASK & 1) {
if (ORDER == 0) {
if (ORDER == 0) {
if (MASK & 0x1) {
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 {
}
} else {
if (MASK & 0x4) {
for (lfs_size_t i = 0; i < CHUNK; i++) {
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
}
@@ -700,15 +702,17 @@ code = '''
}
// write third chunk?
if (MASK & 4) {
if (ORDER == 0) {
if (ORDER == 0) {
if (MASK & 0x4) {
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 {
}
} else {
if (MASK & 0x1) {
for (lfs_size_t i = 0; i < CHUNK; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
@@ -774,7 +778,7 @@ defines.CHUNK = ['CACHE_SIZE/2', '4', '1']
defines.MASK = [0, 1, 2, 3, 4, 5, 6, 7]
# 0 => in-order
# 1 => reversed
defines.ORDER = [0] # TODO 1?
defines.ORDER = [0, 1]
defines.SYNC = [false, true]
defines.REMOUNT = [false, true]
reentrant = true
@@ -816,15 +820,17 @@ code = '''
}
// write first chunk?
if (MASK & 1) {
if (ORDER == 0) {
if (ORDER == 0) {
if (MASK & 0x1) {
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 {
}
} else {
if (MASK & 0x4) {
for (lfs_size_t i = 0; i < CHUNK; i++) {
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
}
@@ -872,15 +878,17 @@ code = '''
}
// write third chunk?
if (MASK & 4) {
if (ORDER == 0) {
if (ORDER == 0) {
if (MASK & 0x4) {
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 {
}
} else {
if (MASK & 0x1) {
for (lfs_size_t i = 0; i < CHUNK; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
@@ -936,10 +944,10 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# writing any data structure backwards always reveals issues
[cases.test_files_reversed_overwrite]
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
defines.CHUNK = ['CACHE_SIZE/2', '4', '1']
# simple truncate test
[cases.test_files_truncate]
defines.FROM = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE']
defines.TO = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE']
defines.SYNC = [false, true]
defines.REMOUNT = [false, true]
reentrant = true
@@ -957,12 +965,13 @@ code = '''
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
// simulate our file in ram
uint8_t sim[SIZE];
uint8_t sim[lfs_max32(FROM,TO)];
memset(sim, 0, lfs_max32(FROM,TO));
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
for (lfs_size_t i = 0; i < FROM; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
lfsr_file_write(&lfs, &file, sim, FROM) => FROM;
// sync?
if (SYNC) {
@@ -977,27 +986,10 @@ code = '''
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;
// truncate to new size
lfsr_file_truncate(&lfs, &file, TO) => 0;
// 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;
}
}
// close
lfsr_file_close(&lfs, &file) => 0;
// remount?
@@ -1011,7 +1003,7 @@ code = '''
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
assert(info.size == TO);
// and with dir read
lfsr_dir_t dir;
@@ -1025,29 +1017,147 @@ code = '''
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
assert(info.size == TO);
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;
lfsr_file_size(&lfs, &file) => TO;
// try reading
uint8_t rbuf[2*SIZE];
memset(rbuf, 0xaa, 2*SIZE);
lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => SIZE;
// does our file match our simulation?
assert(memcmp(rbuf, sim, SIZE) == 0);
uint8_t rbuf[2*TO];
memset(rbuf, 0xaa, 2*TO);
lfsr_file_read(&lfs, &file, rbuf, 2*TO) => TO;
assert(memcmp(rbuf, sim, TO) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# the main purpose of this test is to check that data is not hidden
# and then revealed by truncate, that would be bad
[cases.test_files_truncate_2]
defines.FROM = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE']
defines.AND = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE']
defines.TO = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE']
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[lfs_max32(FROM,TO)];
memset(sim, 0, lfs_max32(FROM,TO));
uint32_t prng = 42;
for (lfs_size_t i = 0; i < FROM; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, FROM) => FROM;
// 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;
}
// truncate to intermediate size
lfsr_file_truncate(&lfs, &file, AND) => 0;
memset(&sim[AND], 0, lfs_max32(FROM,TO)
- lfs_min32(AND, lfs_max32(FROM,TO)));
// 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;
}
// truncate to new size
lfsr_file_truncate(&lfs, &file, TO) => 0;
// close
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 == TO);
// 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 == TO);
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) => TO;
// try reading
uint8_t rbuf[2*TO];
memset(rbuf, 0xaa, 2*TO);
lfsr_file_read(&lfs, &file, rbuf, 2*TO) => TO;
assert(memcmp(rbuf, sim, TO) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# TODO
# [cases.test_files_fruncate]
# [cases.test_files_fruncate_2]
# writing any data structure backwards always reveals issues
[cases.test_files_reversed_holes]
[cases.test_files_reversed]
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
defines.CHUNK = ['CACHE_SIZE/2', '4', '1']
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [false, true]
defines.REMOUNT = [false, true]
reentrant = true
@@ -1067,7 +1177,17 @@ code = '''
// simulate our file in ram
uint8_t sim[SIZE];
uint32_t prng = 42;
memset(sim, 0, SIZE);
if (INIT == 0) {
memset(sim, 0, SIZE);
} else if (INIT == 1) {
for (lfs_size_t i = 0; i < SIZE; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
} else if (INIT == 2) {
memset(sim, 0, SIZE);
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
}
// sync?
if (SYNC) {
@@ -1155,7 +1275,10 @@ defines.N = 100
defines.SEED = 'range(100)'
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
defines.CHUNK = ['CACHE_SIZE/2', '4', '1']
defines.INIT = [false, true]
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [false, true]
defines.REMOUNT = [false, true]
code = '''
@@ -1171,15 +1294,19 @@ code = '''
uint8_t sim[SIZE];
lfs_off_t size;
uint32_t prng = SEED;
if (INIT) {
if (INIT == 0) {
memset(sim, 0, SIZE);
size = 0;
} else if (INIT == 1) {
for (lfs_size_t i = 0; i < SIZE; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
size = SIZE;
} else {
} else if (INIT == 2) {
memset(sim, 0, SIZE);
size = 0;
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
size = SIZE;
}
// sync?
@@ -1275,7 +1402,10 @@ defines.SEED = 'range(100)'
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
# chunk is more an upper limit here
defines.CHUNK = ['CACHE_SIZE/2', '4']
defines.INIT = [false, true]
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [false, true]
defines.REMOUNT = [false, true]
code = '''
@@ -1291,15 +1421,19 @@ code = '''
uint8_t sim[SIZE];
lfs_off_t size;
uint32_t prng = SEED;
if (INIT) {
if (INIT == 0) {
memset(sim, 0, SIZE);
size = 0;
} else if (INIT == 1) {
for (lfs_size_t i = 0; i < SIZE; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
size = SIZE;
} else {
} else if (INIT == 2) {
memset(sim, 0, SIZE);
size = 0;
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
size = SIZE;
}
// sync?
@@ -1469,7 +1603,10 @@ defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END']
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
# chunk is more an upper limit here
defines.CHUNK = ['CACHE_SIZE/2', '4']
defines.INIT = [false, true]
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [false, true]
code = '''
lfs_t lfs;
@@ -1484,15 +1621,19 @@ code = '''
uint8_t sim[SIZE];
lfs_off_t size;
uint32_t prng = SEED;
if (INIT) {
if (INIT == 0) {
memset(sim, 0, SIZE);
size = 0;
} else if (INIT == 1) {
for (lfs_size_t i = 0; i < SIZE; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
size = SIZE;
} else {
} else if (INIT == 2) {
memset(sim, 0, SIZE);
size = 0;
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
size = SIZE;
}
lfsr_file_close(&lfs, &file) => 0;
@@ -1588,7 +1729,10 @@ defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END']
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
# chunk is more an upper limit here
defines.CHUNK = ['CACHE_SIZE/2', '4']
defines.INIT = [false, true]
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [false, true]
code = '''
lfs_t lfs;
@@ -1603,15 +1747,19 @@ code = '''
uint8_t sim[SIZE];
lfs_off_t size;
uint32_t prng = SEED;
if (INIT) {
if (INIT == 0) {
memset(sim, 0, SIZE);
size = 0;
} else if (INIT == 1) {
for (lfs_size_t i = 0; i < SIZE; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
size = SIZE;
} else {
} else if (INIT == 2) {
memset(sim, 0, SIZE);
size = 0;
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
size = SIZE;
}
lfsr_file_close(&lfs, &file) => 0;
@@ -1727,7 +1875,10 @@ code = '''
[cases.test_files_seek_negative]
defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END']
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
defines.INIT = [false, true]
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.MODE = ['LFS_O_RDONLY', 'LFS_O_WRONLY', 'LFS_O_RDWR']
code = '''
lfs_t lfs;
@@ -1742,15 +1893,19 @@ code = '''
uint8_t sim[SIZE];
lfs_off_t size;
uint32_t prng = 42;
if (INIT) {
if (INIT == 0) {
memset(sim, 0, SIZE);
size = 0;
} else if (INIT == 1) {
for (lfs_size_t i = 0; i < SIZE; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
size = SIZE;
} else {
} else if (INIT == 2) {
memset(sim, 0, SIZE);
size = 0;
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
size = SIZE;
}
lfsr_file_close(&lfs, &file) => 0;
@@ -1803,11 +1958,178 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# heavy fuzz test with rw seeks, truncate, and TODO fruncate
[cases.test_files_rwtf_fuzz]
defines.N = 100
defines.SEED = 'range(100)'
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
# chunk is more an upper limit here
defines.CHUNK = ['CACHE_SIZE/2', '4']
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [false, true]
defines.REMOUNT = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
// simulate our file in ram
uint8_t sim[SIZE];
lfs_off_t size;
uint32_t prng = SEED;
if (INIT == 0) {
memset(sim, 0, SIZE);
size = 0;
} else if (INIT == 1) {
for (lfs_size_t i = 0; i < SIZE; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
size = SIZE;
} else if (INIT == 2) {
memset(sim, 0, SIZE);
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
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_RDWR) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
// and if we are reading, writing, or truncating
uint8_t op = TEST_PRNG(&prng) % 3;
// writing?
if (op == 0) {
// choose a random location
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
// and a random size, up to the chunk size
lfs_size_t chunk = lfs_min32(
TEST_PRNG(&prng) % CHUNK,
SIZE - off);
// seek
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
// update the sim
for (lfs_size_t j = 0; j < chunk; j++) {
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
}
if (chunk != 0) {
size = lfs_max32(size, off+chunk);
}
// update the file
lfsr_file_write(&lfs, &file, &sim[off], 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_RDWR) => 0;
}
// reading?
} else if (op == 1) {
// choose a random location
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
// and a random size, up to the chunk size
lfs_size_t chunk = lfs_min32(
TEST_PRNG(&prng) % CHUNK,
SIZE - off);
// seek
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
// we may read less than chunk if we're past eof
lfs_off_t expected = lfs_min32(
chunk,
size - lfs_min32(off, size));
// read the file and assert we got the correct data
uint8_t rbuf[2*SIZE];
memset(rbuf, 0xaa, 2*SIZE);
lfsr_file_read(&lfs, &file, rbuf, chunk) => expected;
assert(memcmp(rbuf, &sim[off], expected) == 0);
// truncating?
} else if (op == 2) {
// choose a random new file size
lfs_off_t size_ = TEST_PRNG(&prng) % SIZE;
// update the sim
memset(&sim[size_], 0, size - lfs_min32(size_, size));
size = size_;
// truncate the file
lfsr_file_truncate(&lfs, &file, size_) => 0;
}
}
lfsr_file_close(&lfs, &file) => 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[2*SIZE];
memset(rbuf, 0xaa, 2*SIZE);
lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size;
// does our file match our simulation?
assert(memcmp(rbuf, sim, size) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# TODO
# [cases.test_files_truncate] ?
# [cases.test_files_fruncate] ?
# [cases.test_files_no_hidden_data]
# [cases.test_files_rwtf_fuzz] ?
# [cases.test_files_push] ?
# [cases.test_files_pop] ?