Files
littlefs/tests/test_files.toml
T
Christopher Haster 25c7831417 Fixed clobbered shrubs after renaming over an mdir split
Good news! test_wl_orphanzombie_fuzz found a rare and difficult to reach
bug. Bad news, it found the bug only after changing littlefs's initial
revision count, which is about as unrelated a change as you can possibly
have...

Oh well, at least now we can add specialized tests targeting this (and
push them to hopefully cover anything similar):

- test_files_mv_split
- test_files_mv_split_backwards
- test_forphans_rename_split
- test_forphans_rename_split_backwards

The bug occurs when a rename of a file to/from the same mdir triggers an
mdir split, and you have that file opened, and the opened file handle
tracks a bshrub or bsprout. Oh, and if that wasn't unlikely enough, this
only breaks when the rename crosses from the new-right-sibling to the
new-left-sibling (inverse order of mdir split compacts), left-to-right
is fine.

The problem is how we stage bshrubs/bsprouts. bshrubs/bsprouts are a bit
tricky in that several unrelated operations can change their location,
sometimes multiple times in the same lfsr_mdir_commit call:

- mdir compaction - move bshrub/bsprout to new mdir
- bshrub commit - append a new shrub trunk
- rename commit - move bshrub/bsprout to a new mdir/mid

To keep track of all of this, lfsr_file_t has a dedicated field,
file.bshrub_, that holds the bshrub/bsprout's new location during
lfsr_mdir_commit. This may be changed multiple times, but the last
change wins.

This works as long as changes occur in an expected order. Importantly,
commits that change the bshrub, such as rename, need to play out after
compactions.

It turns out this is violated when splitting an mdir.

Because we have single pcache, we need to write out the entire compact +
commit of each mdir at a time. When we split, we arbitrarily do this
left-to-right, which results in left commits being played out before
right compactions.

Here's how things play out when we rename right-to-left:

1. commit rename                    -> bshrub = src mid, orig mdir
2. commit fails because of ERANGE
3. compact left mdir                -> bshrub = src mid, left mdir
4. commit left mdir                 -> bshrub = dst mid, left mdir
5. compact right mdir               -> bshrub = src mid, right mdir
6. commit right mdir (skips rename)

Oh no! Our staged bshrub ends up with the wrong location.

---

This is quite tricky to solve. We can't just play out the rename again
on the right mdir, because we've already lost the new bshrub trunk at
this point. Other solutions involving the grm or extra "moved" flags get
messy because, well, lfsr_mdir_commit's internals are quite messy.

The solution here, which is a bit hacky, but also obnoxiously elegant in
a way, is to reorder the split mdir compactions such that the new mdir
containing the commit mid is always compacted last. The means any
related attrs are played out after both compactions, allowing renames to
resolve correctly:

1. commit rename                    -> bshrub = src mid, orig mdir
2. commit fails because of ERANGE
3. right mdir contains mid
4. compact right mdir               -> bshrub = src mid, right mdir
5. commit right mdir (skips rename)
6. compact left mdir                -> bshrub = src mid, left mdir
7. commit left mdir                 -> bshrub = dst mid, left mdir

This only works as long as such commits only span a single mid, though
we already rely on mdir commits being single-mid elsewhere, so maybe
this won't be a problem?

The only real remaining concern is how much complexity this adds to
lfsr_mdir_commit. And while this feels logically messy, the resulting
code cost is surprisingly little:

           code          stack
  before: 33458           2640
  after:  33482 (+0.1%)   2640 (+0.0%)

Still, I'll have to scratch my head to see if there's a better way to
solve this...
2024-05-24 00:04:00 -05:00

2619 lines
78 KiB
TOML

# Test basic file operations
after = ['test_dirs', 'test_btree']
# test creation/deletion
[cases.test_files_create]
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) => 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
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]
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) => 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
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]
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_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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
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_NOTDIR;
// 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
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);
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) => 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);
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
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;
'''
# root is also not a file
[cases.test_files_root_not_file]
defines.REMOUNT = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// try reading our root as a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "/", LFS_O_RDONLY) => LFS_ERR_INVAL;
// try writing our root as a file
lfsr_file_open(&lfs, &file, "/", LFS_O_WRONLY) => LFS_ERR_INVAL;
lfsr_file_open(&lfs, &file, "/",
LFS_O_WRONLY | LFS_O_TRUNC) => LFS_ERR_INVAL;
lfsr_file_open(&lfs, &file, "/",
LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
lfsr_file_open(&lfs, &file, "/",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => LFS_ERR_INVAL;
// 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", "/") => LFS_ERR_INVAL;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check our root with stat
struct lfs_info info;
lfsr_stat(&lfs, "/", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS_TYPE_DIR);
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
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;
'''
# an invalid path is also not a file (kind of?)
[cases.test_files_noent_not_file]
defines.REMOUNT = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// try reading our invalid path as a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "no/hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
// try writing our root as a file
lfsr_file_open(&lfs, &file, "no/hello", LFS_O_WRONLY) => LFS_ERR_NOENT;
lfsr_file_open(&lfs, &file, "no/hello",
LFS_O_WRONLY | LFS_O_TRUNC) => LFS_ERR_NOENT;
lfsr_file_open(&lfs, &file, "no/hello",
LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
lfsr_file_open(&lfs, &file, "no/hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => LFS_ERR_NOENT;
// try rename a file on top of our invalid path
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", "no/hello") => LFS_ERR_NOENT;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check our root with stat
struct lfs_info info;
lfsr_stat(&lfs, "/", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS_TYPE_DIR);
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
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
#
# note:
# - at 2*FBUFFER_SIZE we need a shrub
# - at BLOCK_SIZE/2 we need a block pointer
# - at 2*BLOCK_SIZE we need a btree
#
[cases.test_files_more]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
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) => 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
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;
'''
# test creating multiple files
[cases.test_files_many]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
if = [
'(SIZE*N)/BLOCK_SIZE <= 32',
# limit powerloss testing due to time
# TODO can this be increased after optimizing file writes?
'!TEST_PLS || (SIZE*N) <= 1*BLOCK_SIZE',
]
reentrant = true
code = '''
// format once per test
lfs_t lfs;
int err = lfsr_mount(&lfs, CFG);
if (err) {
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// create this many files
uint32_t prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "amethyst%03x", i);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
// TODO remove this eventually?
//
// file may exist from a previous run, but we need to check
// for pesky zero-sized files
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_ssize_t size = lfsr_file_size(&lfs, &file);
assert(size == 0 || size == SIZE);
if (size == 0) {
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
}
lfsr_file_close(&lfs, &file) => 0;
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our writes worked
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "amethyst%03x", i);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
uint8_t rbuf[SIZE];
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
# fuzz test file creation
[cases.test_files_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64]
# do more ops than files to encourage file rewrites
defines.DENSITY = 2
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.SEED = 'range(20)'
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY);
// associate each file with a prng that generates its contents
uint32_t wprng = TEST_PRNG(&prng);
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// new prng
sim_prngs[j] = wprng;
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
memmove(&sim_prngs[j+1], &sim_prngs[j],
(sim_size-j)*sizeof(uint32_t));
sim_size += 1;
sim[j] = x;
sim_prngs[j] = wprng;
}
break;
}
}
// create a file here
char name[256];
sprintf(name, "amethyst%03x", x);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our files match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// check the file contents
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
# test removing files of various sizes
#
# to be honest, this doesn't really test much and is just included
# for completeness
#
[cases.test_files_rm]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
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;
}
// remove our file
lfsr_remove(&lfs, "amethyst") => 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, "amethyst", &info) => LFS_ERR_NOENT;
// 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test creating and deleting multiple files
[cases.test_files_rm_many]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.REMAINING = [4, 1, 0]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
if = [
'N > REMAINING',
'(SIZE*N)/BLOCK_SIZE <= 32',
# limit powerloss testing due to time
# TODO can this be increased after optimizing file writes?
'!TEST_PLS || ((SIZE*N) <= BLOCK_SIZE/4 && N <= 16)',
]
reentrant = true
code = '''
// format once per test
lfs_t lfs;
int err = lfsr_mount(&lfs, CFG);
if (err) {
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// create this many files
uint32_t prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "amethyst%03x", i);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
// TODO remove this eventually?
//
// file may exist from a previous run, but we need to check
// for pesky zero-sized files
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_ssize_t size = lfsr_file_size(&lfs, &file);
assert(size == 0 || size == SIZE);
if (size == 0) {
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
}
lfsr_file_close(&lfs, &file) => 0;
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our writes worked
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "amethyst%03x", i);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
uint8_t rbuf[SIZE];
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// now remove some number of files
for (lfs_size_t i = 0; i < N-REMAINING; i++) {
char name[256];
sprintf(name, "amethyst%03x", i);
lfsr_remove(&lfs, name) => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
}
// check that our removes worked
//
// note we need to keep the prng in sync
//
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "amethyst%03x", i);
// keep prng in sync
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
if (i < N-REMAINING) {
// check with stat
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => LFS_ERR_NOENT;
// and try to open
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_NOENT;
} else {
// check with stat
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
lfsr_file_t file;
uint8_t rbuf[SIZE];
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
}
lfsr_unmount(&lfs) => 0;
'''
# fuzz test file creation and deletion
[cases.test_files_rm_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64]
# do more ops than files to encourage file rewrites
defines.DENSITY = 2
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.SEED = 'range(20)'
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 2;
// creating a new file?
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY);
// associate each file with a prng that generates its contents
uint32_t wprng = TEST_PRNG(&prng);
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// new prng
sim_prngs[j] = wprng;
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
memmove(&sim_prngs[j+1], &sim_prngs[j],
(sim_size-j)*sizeof(uint32_t));
sim_size += 1;
sim[j] = x;
sim_prngs[j] = wprng;
}
break;
}
}
// create a file here
char name[256];
sprintf(name, "amethyst%03x", x);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// deleting a file?
} else {
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// delete this file
char name[256];
sprintf(name, "amethyst%03x", x);
lfsr_remove(&lfs, name) => 0;
}
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our files match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// check the file contents
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
# test renaming files of various sizes
[cases.test_files_mv]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.N = [0, 128]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a number of directories to distance our files
for (lfs_size_t i = 0; i < N; i++) {
char path[256];
sprintf(path, "basalt%03x", i);
lfsr_mkdir(&lfs, path) => 0;
}
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
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;
}
// rename the file
lfsr_rename(&lfs, "amethyst", "calcite") => 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, "calcite", &info) => 0;
assert(strcmp(info.name, "calcite") == 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t i = 0; i < N; i++) {
char path[256];
sprintf(path, "basalt%03x", i);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
}
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "calcite") == 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, "calcite", 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;
'''
# test renaming files of various sizes over existing files
[cases.test_files_mv_replace]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.N = [0, 128]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a number of directories to distance our files
for (lfs_size_t i = 0; i < N; i++) {
char path[256];
sprintf(path, "basalt%03x", i);
lfsr_mkdir(&lfs, path) => 0;
}
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// create another file
lfsr_file_open(&lfs, &file, "calcite", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf_[SIZE];
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;
}
// rename the file
lfsr_rename(&lfs, "amethyst", "calcite") => 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, "amethyst", &info) => LFS_ERR_NOENT;
lfsr_stat(&lfs, "calcite", &info) => 0;
assert(strcmp(info.name, "calcite") == 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t i = 0; i < N; i++) {
char path[256];
sprintf(path, "basalt%03x", i);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
}
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "calcite") == 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, "calcite", 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;
'''
# test renaming a file to itself
[cases.test_files_mv_noop]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
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;
}
// rename the file
lfsr_rename(&lfs, "amethyst", "amethyst") => 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, "amethyst", &info) => 0;
assert(strcmp(info.name, "amethyst") == 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "amethyst") == 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, "amethyst", 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;
'''
# test renaming a file onto a dir
[cases.test_files_mv_not_file]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// create a dir
lfsr_mkdir(&lfs, "basalt") => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// rename the file
lfsr_rename(&lfs, "amethyst", "basalt") => LFS_ERR_ISDIR;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that nothing changed in our file/dir with stat
struct lfs_info info;
lfsr_stat(&lfs, "amethyst", &info) => 0;
assert(strcmp(info.name, "amethyst") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
lfsr_stat(&lfs, "basalt", &info) => 0;
assert(strcmp(info.name, "basalt") == 0);
assert(info.type == LFS_TYPE_DIR);
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "amethyst") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "basalt") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// try reading our file
lfsr_file_open(&lfs, &file, "amethyst", 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;
'''
# test renaming a dir onto a file
[cases.test_files_mv_not_dir]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// create a dir
lfsr_mkdir(&lfs, "basalt") => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// rename the dir
lfsr_rename(&lfs, "basalt", "amethyst") => LFS_ERR_NOTDIR;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that nothing changed in our file/dir with stat
struct lfs_info info;
lfsr_stat(&lfs, "amethyst", &info) => 0;
assert(strcmp(info.name, "amethyst") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
lfsr_stat(&lfs, "basalt", &info) => 0;
assert(strcmp(info.name, "basalt") == 0);
assert(info.type == LFS_TYPE_DIR);
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "amethyst") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "basalt") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// try reading our file
lfsr_file_open(&lfs, &file, "amethyst", 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;
'''
# test renaming a file onto root
[cases.test_files_mv_not_root]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
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;
}
// rename the file
lfsr_rename(&lfs, "amethyst", "/") => LFS_ERR_INVAL;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that nothing changed in our file/dir with stat
struct lfs_info info;
lfsr_stat(&lfs, "amethyst", &info) => 0;
assert(strcmp(info.name, "amethyst") == 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "amethyst") == 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, "amethyst", 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;
'''
# test renaming a file onto an invalid path
[cases.test_files_mv_not_noent]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.FBUFFER_SIZE = 64
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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0;
uint8_t wbuf[SIZE];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < SIZE; i++) {
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;
}
// rename the file
lfsr_rename(&lfs, "amethyst", "no/amethyst") => LFS_ERR_NOENT;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that nothing changed in our file/dir with stat
struct lfs_info info;
lfsr_stat(&lfs, "amethyst", &info) => 0;
assert(strcmp(info.name, "amethyst") == 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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "amethyst") == 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, "amethyst", 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;
'''
# test renaming multiple files
[cases.test_files_mv_many]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
if = [
'(SIZE*N)/BLOCK_SIZE <= 32',
# limit powerloss testing due to time
# TODO can this be increased after optimizing file writes?
'!TEST_PLS || ((SIZE*N) <= BLOCK_SIZE/4 && N <= 16)',
]
reentrant = true
code = '''
// format once per test
lfs_t lfs;
int err = lfsr_mount(&lfs, CFG);
if (err) {
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// create this many files
uint32_t prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "amethyst%03x", i);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
// TODO remove this eventually?
//
// file may exist from a previous run, but we need to check
// for pesky zero-sized files
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_ssize_t size = lfsr_file_size(&lfs, &file);
assert(size == 0 || size == SIZE);
if (size == 0) {
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
}
lfsr_file_close(&lfs, &file) => 0;
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our writes worked
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "amethyst%03x", i);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
uint8_t rbuf[SIZE];
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// now rename our files
for (lfs_size_t i = 0; i < N; i++) {
char old_name[256];
sprintf(old_name, "amethyst%03x", i);
char new_name[256];
sprintf(new_name, "basalt%03x", i);
lfsr_rename(&lfs, old_name, new_name) => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
}
// check that our renames worked
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "amethyst%03x", i);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => LFS_ERR_NOENT;
sprintf(name, "basalt%03x", i);
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
uint8_t rbuf[SIZE];
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
# one particularly nasty case is renaming over an mdir split, since shrubs
# can be moved around quite a few times when that happens
#
# here we spam renames over an increasing number of files to hopefully hit
# that case
#
[cases.test_files_mv_split]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create this many files while renaming
uint32_t prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// always create as first file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "amethyst",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// rename!
char name[256];
sprintf(name, "basalt%03x", i);
lfsr_rename(&lfs, "amethyst", name) => 0;
}
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that renames worked
prng = 42;
struct lfs_info info;
lfsr_stat(&lfs, "amethyst", &info) => LFS_ERR_NOENT;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "basalt%03x", i);
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// try reading the file
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
uint8_t rbuf[SIZE];
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
[cases.test_files_mv_split_backwards]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create this many files while renaming
uint32_t prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// always create as last file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cobalt",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// rename!
char name[256];
sprintf(name, "basalt%03x", (uint32_t)(N-1-i));
lfsr_rename(&lfs, "cobalt", name) => 0;
}
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that renames worked
prng = 42;
struct lfs_info info;
lfsr_stat(&lfs, "cobalt", &info) => LFS_ERR_NOENT;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "basalt%03x", (uint32_t)(N-1-i));
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// try reading the file
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
uint8_t rbuf[SIZE];
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
# fuzz test file creation and rename
[cases.test_files_mv_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64]
# do more ops than files to encourage file rewrites
defines.DENSITY = 2
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.SEED = 'range(20)'
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 2;
// creating a new file?
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY);
// associate each file with a prng that generates its contents
uint32_t wprng = TEST_PRNG(&prng);
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// new prng
sim_prngs[j] = wprng;
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
memmove(&sim_prngs[j+1], &sim_prngs[j],
(sim_size-j)*sizeof(uint32_t));
sim_size += 1;
sim[j] = x;
sim_prngs[j] = wprng;
}
break;
}
}
// create a file here
char name[256];
sprintf(name, "amethyst%03x", x);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// renaming a file?
} else {
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY);
uint32_t wprng = sim_prngs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "amethyst%03x", x);
char new_name[256];
sprintf(new_name, "amethyst%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our files match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// check the file contents
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
# fuzz test file creation/deletion/rename
[cases.test_files_mvrm_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64]
# do more ops than files to encourage file rewrites
defines.DENSITY = 2
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.SEED = 'range(20)'
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 3;
// creating a new file?
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY);
// associate each file with a prng that generates its contents
uint32_t wprng = TEST_PRNG(&prng);
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// new prng
sim_prngs[j] = wprng;
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
memmove(&sim_prngs[j+1], &sim_prngs[j],
(sim_size-j)*sizeof(uint32_t));
sim_size += 1;
sim[j] = x;
sim_prngs[j] = wprng;
}
break;
}
}
// create a file here
char name[256];
sprintf(name, "amethyst%03x", x);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// deleting a file?
} else if (op == 1) {
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// delete this file
char name[256];
sprintf(name, "amethyst%03x", x);
lfsr_remove(&lfs, name) => 0;
// renaming a file?
} else {
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY);
uint32_t wprng = sim_prngs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "amethyst%03x", x);
char new_name[256];
sprintf(new_name, "amethyst%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our files match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// check the file contents
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
# TODO
# [cases.test_files_multi_readers]
# [cases.test_files_multi_readers_one_writer]
# [cases.test_files_multi_writers]
# [cases.test_files_multi_readers_multi_writers]
# [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]