Files
littlefs/tests/test_files.toml
T
Christopher Haster 39f417db45 Implemented a filesystem traversal that understands file bptrs/btrees
Ended up changing the name of lfsr_mtree_traversal_t -> lfsr_traversal_t,
since this behaves more like a filesytem-wide traversal than an mtree
traversal (it returns several typed objects, not mdirs like the other
mtree functions for one).

As a part of this changeset, lfsr_btraversal_t (was lfsr_btree_traversal_t)
and lfsr_traversal_t no longer return untyped lfsr_data_ts, but instead
return specialized lfsr_{b,t}info_t structs. We weren't even using
lfsr_data_t for its original purpose in lfsr_traversal_t.

Also changed lfsr_traversal_next -> lfsr_traversal_read, you may notice
at this point the changes are intended to make lfsr_traversal_t look
more like lfsr_dir_t for consistency.

---

Internally lfsr_traversal_t now uses a full state machine with its own
enum due to the complexity of traversing the filesystem incrementally.

Because creating diagrams is fun, here's the current full state machine,
though note it will need to be extended for any
parity-trees/free-trees/etc:

  mrootanchor
       |
       v
  mrootchain
  .-'  |
  |    v
  |  mtree ---> openedblock
  '-. | ^           | ^
    v v |           v |
   mdirblock    openedbtree
      | ^
      v |
   mdirbtree

I'm not sure I'm happy with the current implementation, and eventually
it will need to be able to handle in-place repairs to the blocks it
sees, so this whole thing may need a rewrite.

But in the meantime, this passes the new clobber tests in test_alloc, so
it should be enough to prove the file implementation works. (which is
definitely is not fully tested yet, and some bugs had to be fixed for
the new tests in test_alloc to pass).

---

Speaking of test_alloc.

The inherent cyclic dependency between files/dirs/alloc makes it a bit
hard to know what order to test these bits of functionality in.

Originally I was testing alloc first, because it seems you need to be
confident in your block allocator before you can start testing
higher-level data structures.

But I've gone ahead and reversed this order, testing alloc after
files/dirs. This is because of an interesting observation that if alloc
is broken, you can always increase the test device's size to some absurd
number (-DDISK_SIZE=16777216, for example) to kick the can down the
road.

Testing in this order allows alloc to use more high-level APIs and
focus on corner cases where the allocator's behavior requires subtlety
to be correct (e.g. ENOSPC).
2023-10-14 01:13:40 -05:00

2917 lines
88 KiB
TOML

# Test basic file operations
after = ['test_dtree', 'test_btree']
# test creation/deletion
[cases.test_files_create]
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
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => 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 == 0);
// 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 == 0);
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) => 0;
// try reading
uint8_t rbuf[8192];
lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => 0;
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test we can write some data, should be inlined
[cases.test_files_hello]
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
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[8192];
strcpy((char*)wbuf, "Hello World!");
lfs_size_t wsize = strlen((const char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
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 == wsize);
// 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 == wsize);
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) => wsize;
// try reading
uint8_t rbuf[8192];
memset(rbuf, 0xaa, sizeof(rbuf));
lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test we can rewrite a file
[cases.test_files_trunc]
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
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
uint8_t wbuf[8192];
strcpy((char*)wbuf, "Oh no!");
lfs_size_t wsize = strlen((const char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// rewrite the file
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
strcpy((char*)wbuf, "Hello World!");
wsize = strlen((const char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
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 == wsize);
// 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 == wsize);
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) => wsize;
// try reading
uint8_t rbuf[8192];
memset(rbuf, 0xaa, sizeof(rbuf));
lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# check for LFS_F_EXCL errors
[cases.test_files_excl]
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_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[8192];
strcpy((char*)wbuf, "Hello World!");
lfs_size_t wsize = strlen((const char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// try to recreate file, this should error
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
// 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 == wsize);
// 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 == wsize);
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) => wsize;
// try reading
uint8_t rbuf[8192];
memset(rbuf, 0xaa, sizeof(rbuf));
lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# a file is not a directory
[cases.test_files_file_not_dir]
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_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[8192];
strcpy((char*)wbuf, "Hello World!");
lfs_size_t wsize = strlen((const char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// try to open our file as a directory
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "hello") => LFS_ERR_NOTDIR;
// try to create a directory on top of our file
lfsr_mkdir(&lfs, "hello") => LFS_ERR_EXIST;
// try to rename a directory onto our file
lfsr_mkdir(&lfs, "not_hello") => 0;
lfsr_rename(&lfs, "not_hello", "hello") => LFS_ERR_ISDIR;
// 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 == wsize);
// and with dir read
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 == wsize);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "not_hello") == 0);
assert(info.type == LFS_TYPE_DIR);
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) => wsize;
// try reading
uint8_t rbuf[8192];
memset(rbuf, 0xaa, sizeof(rbuf));
lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# a directory is not a file
[cases.test_files_dir_not_file]
defines.REMOUNT = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a directory
lfsr_mkdir(&lfs, "hello") => 0;
// try reading our directory as a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => LFS_ERR_ISDIR;
// try writing our directory as a file
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => LFS_ERR_ISDIR;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_TRUNC) => LFS_ERR_ISDIR;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => LFS_ERR_ISDIR;
// try rename a file on top of our directory
lfsr_file_open(&lfs, &file, "not_hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[8192];
strcpy((char*)wbuf, "Hello World!");
lfs_size_t wsize = strlen((const char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
lfsr_rename(&lfs, "not_hello", "hello") => LFS_ERR_ISDIR;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check our dir with stat
struct lfs_info info;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_DIR);
// 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_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "not_hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// did we corrupt our renaming file?
// try reading our file
lfsr_file_open(&lfs, &file, "not_hello", LFS_O_RDONLY) => 0;
// is size correct?
lfsr_file_size(&lfs, &file) => wsize;
// try reading
uint8_t rbuf[8192];
memset(rbuf, 0xaa, sizeof(rbuf));
lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# try writing larger files?
#
# at 2*CACHE_SIZE we need an inlined tree
# ? single block?
# at 2*BLOCK_SIZE we need a b-tree
#
[cases.test_files_more]
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
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
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
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 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;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# more complex writing patterns to inlined files
# write files incrementally
[cases.test_files_incr]
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;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
lfsr_file_write(&lfs, &file, &wbuf[i], 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;
// note the switch to append here
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_APPEND) => 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[2*SIZE];
memset(rbuf, 0xaa, 2*SIZE);
lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# overwrite files
[cases.test_files_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[SIZE];
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 (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 {
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;
}
}
// 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 (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 {
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;
}
}
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[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;
'''
# similar to overwrite files, but without underlying data
[cases.test_files_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 = [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[SIZE];
uint32_t prng = 42;
memset(sim, 0, SIZE);
// we may not write the entire file
lfs_off_t size
= (MASK & 0x4) ? SIZE
: (MASK & 0x2) ? SIZE/2 + (CHUNK+2-1)/2
: (MASK & 0x1) ? CHUNK
: 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;
}
// write first chunk?
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 {
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;
}
}
// 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 (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 {
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;
}
}
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[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;
'''
# 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
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 new size
lfsr_file_truncate(&lfs, &file, TO) => 0;
if (TO < FROM) {
memset(sim+TO, 0, FROM-TO);
}
// 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;
'''
# one 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,lfs_max32(AND,TO))];
memset(sim, 0, lfs_max32(FROM,lfs_max32(AND,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;
if (AND < FROM) {
memset(sim+AND, 0, FROM-AND);
}
// 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;
if (TO < AND) {
memset(sim+TO, 0, AND-TO);
}
// 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;
'''
# simple fruncate test
[cases.test_files_fruncate]
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
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;
}
// fruncate to new size
lfsr_file_fruncate(&lfs, &file, TO) => 0;
if (TO > FROM) {
memmove(sim+TO-FROM, sim, FROM);
memset(sim, 0, TO-FROM);
} else if (TO < FROM) {
memmove(sim, sim+FROM-TO, TO);
memset(sim+TO, 0, FROM-TO);
}
// 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;
'''
# one purpose of this test is to check that data is not hidden
# and then revealed by fruncate, that would be bad
[cases.test_files_fruncate_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,lfs_max32(AND,TO))];
memset(sim, 0, lfs_max32(FROM,lfs_max32(AND,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;
}
// fruncate to intermediate size
lfsr_file_fruncate(&lfs, &file, AND) => 0;
if (AND > FROM) {
memmove(sim+AND-FROM, sim, FROM);
memset(sim, 0, AND-FROM);
} else if (AND < FROM) {
memmove(sim, sim+FROM-AND, AND);
memset(sim+AND, 0, FROM-AND);
}
// 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;
}
// fruncate to new size
lfsr_file_fruncate(&lfs, &file, TO) => 0;
if (TO > AND) {
memmove(sim+TO-AND, sim, AND);
memset(sim, 0, TO-AND);
} else if (TO < AND) {
memmove(sim, sim+AND-TO, TO);
memset(sim+TO, 0, AND-TO);
}
// 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;
'''
# writing any data structure backwards always reveals issues
[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
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[SIZE];
uint32_t prng = 42;
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) {
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[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;
'''
# fuzz testing
[cases.test_files_fuzz_aligned]
defines.N = 100
defines.SEED = 'range(100)'
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]
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_WRONLY | 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_WRONLY) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
// choose a random chunk-aligned location
lfs_off_t off = (TEST_PRNG(&prng) % (SIZE/CHUNK)) * CHUNK;
// update sim
for (lfs_size_t j = 0; j < CHUNK; j++) {
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
}
size = lfs_max32(size, off+CHUNK);
// update file
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
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_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[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;
'''
# fuzz testing
[cases.test_files_fuzz_unaligned]
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_WRONLY | 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_WRONLY) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
// 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);
// update 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 file
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
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_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[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;
'''
# more seek testing
[cases.test_files_r_seek]
defines.N = 100
defines.SEED = 'range(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']
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_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
// simulate our file in ram
uint8_t sim[SIZE];
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < SIZE; i++) {
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfs_soff_t off_ = 0;
for (lfs_size_t i = 0; i < N; i++) {
// choose a random location
lfs_soff_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);
// test different seek methods
if (WHENCE == LFS_SEEK_SET) {
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
} else if (WHENCE == LFS_SEEK_CUR) {
lfsr_file_seek(&lfs, &file, off-off_, LFS_SEEK_CUR) => off;
} else if (WHENCE == LFS_SEEK_END) {
lfsr_file_seek(&lfs, &file, off-SIZE, LFS_SEEK_END) => off;
}
// tell should always report the correct position
lfsr_file_tell(&lfs, &file) => off;
// 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) => chunk;
assert(memcmp(rbuf, &sim[off], chunk) == 0);
// tell should report the new position
lfsr_file_tell(&lfs, &file) => off + chunk;
// keep track of previous off for LFS_SEEK_CUR
off_ = off + chunk;
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# this is pretty much the same as earlier fuzz testing, except we test
# different seek methods
[cases.test_files_w_seek]
defines.N = 100
defines.SEED = 'range(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']
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [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_WRONLY | 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;
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
lfs_soff_t off_ = 0;
for (lfs_size_t i = 0; i < N; i++) {
// 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);
// test different seek methods
if (WHENCE == LFS_SEEK_SET) {
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
} else if (WHENCE == LFS_SEEK_CUR) {
lfsr_file_seek(&lfs, &file, off-off_, LFS_SEEK_CUR) => off;
} else if (WHENCE == LFS_SEEK_END) {
lfsr_file_seek(&lfs, &file, off-size, LFS_SEEK_END) => off;
}
// tell should always report the correct position
lfsr_file_tell(&lfs, &file) => 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;
}
// tell should report the new position
lfsr_file_tell(&lfs, &file) => off + chunk;
// keep track of previous off for LFS_SEEK_CUR
off_ = off + chunk;
}
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;
'''
# the above was just warmup, here's the real seek test
[cases.test_files_rw_seek]
defines.N = 100
defines.SEED = 'range(100)'
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']
# 0 => no init
# 1 => fill with data
# 2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [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_WRONLY | 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;
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDWR) => 0;
lfs_soff_t off_ = 0;
for (lfs_size_t i = 0; i < N; i++) {
// 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);
// and if we are reading or writing
uint8_t op = TEST_PRNG(&prng) % 2;
// test different seek methods
if (WHENCE == LFS_SEEK_SET) {
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
} else if (WHENCE == LFS_SEEK_CUR) {
lfsr_file_seek(&lfs, &file, off-off_, LFS_SEEK_CUR) => off;
} else if (WHENCE == LFS_SEEK_END) {
lfsr_file_seek(&lfs, &file, off-size, LFS_SEEK_END) => off;
}
// tell should always report the correct position
lfsr_file_tell(&lfs, &file) => off;
// writing?
if (op == 0) {
// 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;
}
// tell should report the new position
lfsr_file_tell(&lfs, &file) => off + chunk;
// keep track of previous off for LFS_SEEK_CUR
off_ = off + chunk;
// reading?
} else if (op == 1) {
// 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);
// tell should report the new position
lfsr_file_tell(&lfs, &file) => off + expected;
// keep track of previous off for LFS_SEEK_CUR
off_ = off + expected;
}
}
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;
'''
# test other corner conditions
[cases.test_files_seek_negative]
defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END']
defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE']
# 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;
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_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
// simulate our file in ram
uint8_t sim[SIZE];
lfs_off_t size;
uint32_t prng = 42;
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;
}
lfsr_file_close(&lfs, &file) => 0;
// try to seek before the beginning of the file, this should fail
lfsr_file_open(&lfs, &file, "hello", MODE) => 0;
if (WHENCE == LFS_SEEK_SET) {
lfsr_file_seek(&lfs, &file, -1, LFS_SEEK_SET) => LFS_ERR_INVAL;
} else if (WHENCE == LFS_SEEK_CUR) {
lfsr_file_seek(&lfs, &file, -1, LFS_SEEK_CUR) => LFS_ERR_INVAL;
} else if (WHENCE == LFS_SEEK_END) {
lfsr_file_seek(&lfs, &file, -(size+1), LFS_SEEK_END) => LFS_ERR_INVAL;
}
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;
'''
# heavy fuzz test with rw seeks, truncate, and 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, truncating, or fruncating
uint8_t op = TEST_PRNG(&prng) % 4;
// 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
if (size_ < size) {
memset(sim+size_, 0, size-size_);
}
size = size_;
// truncate the file
lfsr_file_truncate(&lfs, &file, size_) => 0;
} else if (op == 3) {
// choose a random new file size
lfs_off_t size_ = TEST_PRNG(&prng) % SIZE;
// update the sim
if (size_ > size) {
memmove(sim+size_-size, sim, size);
memset(sim, 0, size_-size);
} else if (size_ < size) {
memmove(sim, sim+size-size_, size_);
memset(sim+size_, 0, size-size_);
}
size = size_;
// truncate the file
lfsr_file_fruncate(&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_push] ?
# [cases.test_files_pop] ?
# [cases.test_files_rwtfpp_fuzz] ?
# [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]
# [cases.test_files_dtree_fuzz]
# [cases.test_files_dtree_fuzz_fuzz]
#
#[cases.test_files_simple]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "hello",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_size_t size = strlen("Hello World!")+1;
# uint8_t buffer[1024];
# strcpy((char*)buffer, "Hello World!");
# lfs_file_write(&lfs, &file, buffer, size) => size;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
# lfs_file_read(&lfs, &file, buffer, size) => size;
# assert(strcmp((char*)buffer, "Hello World!") == 0);
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_large]
#defines.SIZE = [32, 8192, 262144, 0, 7, 8193]
#defines.CHUNKSIZE = [31, 16, 33, 1, 1023]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# // write
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "avacado",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# uint32_t prng = 1;
# uint8_t buffer[1024];
# for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // read
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => SIZE;
# prng = 1;
# for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_rewrite]
#defines.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
#defines.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
#defines.CHUNKSIZE = [31, 16, 1]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# // write
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# uint8_t buffer[1024];
# lfs_file_open(&lfs, &file, "avacado",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# uint32_t prng = 1;
# for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // read
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => SIZE1;
# prng = 1;
# for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // rewrite
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY) => 0;
# prng = 2;
# for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // read
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => lfs_max(SIZE1, SIZE2);
# prng = 2;
# for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# if (SIZE1 > SIZE2) {
# prng = 1;
# for (lfs_size_t b = 0; b < SIZE2; b++) {
# TEST_PRNG(&prng);
# }
# for (lfs_size_t i = SIZE2; i < SIZE1; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_append]
#defines.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
#defines.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
#defines.CHUNKSIZE = [31, 16, 1]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# // write
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# uint8_t buffer[1024];
# lfs_file_open(&lfs, &file, "avacado",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# uint32_t prng = 1;
# for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // read
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => SIZE1;
# prng = 1;
# for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // append
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_APPEND) => 0;
# prng = 2;
# for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // read
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => SIZE1 + SIZE2;
# prng = 1;
# for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# prng = 2;
# for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_truncate]
#defines.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
#defines.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
#defines.CHUNKSIZE = [31, 16, 1]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# // write
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# uint8_t buffer[1024];
# lfs_file_open(&lfs, &file, "avacado",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# uint32_t prng = 1;
# for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // read
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => SIZE1;
# prng = 1;
# for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // truncate
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_TRUNC) => 0;
# prng = 2;
# for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# // read
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => SIZE2;
# prng = 2;
# for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_reentrant_write]
#defines.SIZE = [32, 0, 7, 2049]
#defines.CHUNKSIZE = [31, 16, 65]
#reentrant = true
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
#
# lfs_file_t file;
# uint8_t buffer[1024];
# err = lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY);
# assert(err == LFS_ERR_NOENT || err == 0);
# if (err == 0) {
# // can only be 0 (new file) or full size
# lfs_size_t size = lfs_file_size(&lfs, &file);
# assert(size == 0 || size == SIZE);
# lfs_file_close(&lfs, &file) => 0;
# }
#
# // write
# lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# uint32_t prng = 1;
# for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# // read
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => SIZE;
# prng = 1;
# for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_reentrant_write_sync]
#defines = [
# # append (O(n))
# {MODE='LFS_O_APPEND', SIZE=[32, 0, 7, 2049], CHUNKSIZE=[31, 16, 65]},
# # truncate (O(n^2))
# {MODE='LFS_O_TRUNC', SIZE=[32, 0, 7, 200], CHUNKSIZE=[31, 16, 65]},
# # rewrite (O(n^2))
# {MODE=0, SIZE=[32, 0, 7, 200], CHUNKSIZE=[31, 16, 65]},
#]
#reentrant = true
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
#
# lfs_file_t file;
# uint8_t buffer[1024];
# err = lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY);
# assert(err == LFS_ERR_NOENT || err == 0);
# if (err == 0) {
# // with syncs we could be any size, but it at least must be valid data
# lfs_size_t size = lfs_file_size(&lfs, &file);
# assert(size <= SIZE);
# uint32_t prng = 1;
# for (lfs_size_t i = 0; i < size; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, size-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_close(&lfs, &file) => 0;
# }
#
# // write
# lfs_file_open(&lfs, &file, "avacado",
# LFS_O_WRONLY | LFS_O_CREAT | MODE) => 0;
# lfs_size_t size = lfs_file_size(&lfs, &file);
# assert(size <= SIZE);
# uint32_t prng = 1;
# lfs_size_t skip = (MODE == LFS_O_APPEND) ? size : 0;
# for (lfs_size_t b = 0; b < skip; b++) {
# TEST_PRNG(&prng);
# }
# for (lfs_size_t i = skip; i < SIZE; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
# for (lfs_size_t b = 0; b < chunk; b++) {
# buffer[b] = TEST_PRNG(&prng) & 0xff;
# }
# lfs_file_write(&lfs, &file, buffer, chunk) => chunk;
# lfs_file_sync(&lfs, &file) => 0;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# // read
# lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0;
# lfs_file_size(&lfs, &file) => SIZE;
# prng = 1;
# for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
# lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i);
# lfs_file_read(&lfs, &file, buffer, chunk) => chunk;
# for (lfs_size_t b = 0; b < chunk; b++) {
# assert(buffer[b] == (TEST_PRNG(&prng) & 0xff));
# }
# }
# lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_many]
#defines.N = 300
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# // create N files of 7 bytes
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# lfs_file_t file;
# char path[1024];
# sprintf(path, "file_%03d", i);
# lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# char wbuffer[1024];
# lfs_size_t size = 7;
# sprintf(wbuffer, "Hi %03d", i);
# lfs_file_write(&lfs, &file, wbuffer, size) => size;
# lfs_file_close(&lfs, &file) => 0;
#
# char rbuffer[1024];
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
# assert(strcmp(rbuffer, wbuffer) == 0);
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_many_power_cycle]
#defines.N = 300
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# // create N files of 7 bytes
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# lfs_file_t file;
# char path[1024];
# sprintf(path, "file_%03d", i);
# lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# char wbuffer[1024];
# lfs_size_t size = 7;
# sprintf(wbuffer, "Hi %03d", i);
# lfs_file_write(&lfs, &file, wbuffer, size) => size;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# char rbuffer[1024];
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
# assert(strcmp(rbuffer, wbuffer) == 0);
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_files_many_power_loss]
#defines.N = 300
#reentrant = true
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
# // create N files of 7 bytes
# for (int i = 0; i < N; i++) {
# lfs_file_t file;
# char path[1024];
# sprintf(path, "file_%03d", i);
# err = lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT);
# char wbuffer[1024];
# lfs_size_t size = 7;
# sprintf(wbuffer, "Hi %03d", i);
# if ((lfs_size_t)lfs_file_size(&lfs, &file) != size) {
# lfs_file_write(&lfs, &file, wbuffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# char rbuffer[1024];
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
# assert(strcmp(rbuffer, wbuffer) == 0);
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''