a0b3eccf15
- test_fwrite_reversed_litmus_fragments
- test_fwrite_reversed_litmus_blocks
- test_fwrite_freversed
- test_fwrite_freversed_litmus_fragments
- test_fwrite_freversed_litmus_blocks
- test_fwrite_truncate_pos
- test_fwrite_fruncate_pos
And hey, they found some bugs:
- crystal_thresh=-1 was broken due to integer overflow in some signed
math.
Fortunately when crystal_thresh=-1 we can just skip the crystal
lookups entirely. This saves a btree lookup in fully-fragmented files.
- We were including empty fragments in our crystal size, when we should
only use them to determine crystal boundaries, like bptrs.
This is a common case for the first entry in a sparse file.
- We weren't updating pos on fruncate. fruncate's effect on pos was
actually not tested at all.
Which raises the question, what should the behavior be? Match
lfsr_file_truncate and leave the pos unaffected?
I ended up having fruncate update the file pos to keep the same pos
relative to the end, as I figured this would have the least surprise
for users. So lfsr_file_read should return the same bytes unless
clobbered.
This is almost a mirror image of lfsr_file_truncate, except we don't
allow negative positions, so fruncating more than pos forces pos to 0.
---
This behavior is now covered in a couple tests:
- test_fwrite_truncate_pos
- test_fwrite_fruncate_pos
- test_fwrite_freversed
- test_fwrite_freversed_litmus_fragments
- test_fwrite_freversed_litmus_blocks
Code changes:
code stack ctx
before: 35688 2440 640
after: 35692 (+0.0%) 2440 (+0.0%) 640 (+0.0%)
4862 lines
146 KiB
TOML
4862 lines
146 KiB
TOML
# More extensive file writing tests
|
|
after = 'test_files'
|
|
|
|
# TODO should fragment_size accept 0?
|
|
# test with different fragment sizes
|
|
defines.FRAGMENT_SIZE = [1, 16, 64]
|
|
|
|
# test with different crystallization thresholds
|
|
defines.CRYSTAL_THRESH = [512]
|
|
|
|
# test with different fragment thresholds
|
|
defines.FRAGMENT_THRESH = [-1]
|
|
|
|
# test with different prog sizes
|
|
defines.PROG_SIZE = [1, 16]
|
|
|
|
|
|
# simple file writes
|
|
[cases.test_fwrite_simple]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SYNC = [false, true]
|
|
if = [
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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;
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 that simple fragment-aligned writes are optimal
|
|
[cases.test_fwrite_simple_litmus_fragments]
|
|
defines.N = [0, 1, 2, 3, 4]
|
|
defines.SIZE = 'N*FRAGMENT_SIZE'
|
|
# force a btree node
|
|
defines.INLINE_SIZE = 0
|
|
defines.CRYSTAL_THRESH = -1
|
|
defines.SYNC = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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;
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of fragments? we need our internal btree traversal
|
|
// API to check this
|
|
//
|
|
lfs_size_t fragments = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many fragments we've seen
|
|
fragments += 1;
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// we disabled block crystallization so this shouldn't
|
|
// happen
|
|
assert(false);
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of fragments?
|
|
assert(fragments == N);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that simple block-aligned writes always end up as compact blocks
|
|
[cases.test_fwrite_simple_litmus_blocks]
|
|
defines.N = [0, 1, 2, 3, 4]
|
|
defines.SIZE = 'N*BLOCK_SIZE'
|
|
defines.SYNC = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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;
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of branches/blocks? we need our internal btree
|
|
// traversal API to check this
|
|
//
|
|
lfs_block_t blocks = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// if block crystallization is working we shouldn't be
|
|
// left with any inlined data fragments
|
|
assert(false);
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many data blocks we've seen
|
|
blocks += 1;
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of blocks?
|
|
assert(blocks == N);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# write files incrementally
|
|
[cases.test_fwrite_incr]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.CHUNK = [32, 8, 1]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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, LFS_M_RDWR, 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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 that incremental fragment-aligned writes are optimal
|
|
[cases.test_fwrite_incr_litmus_fragments]
|
|
defines.N = [0, 1, 2, 3, 4]
|
|
defines.SIZE = 'N*FRAGMENT_SIZE'
|
|
defines.CHUNK = [32, 8, 1]
|
|
# force a btree node
|
|
defines.INLINE_SIZE = 0
|
|
defines.CRYSTAL_THRESH = -1
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = 'CHUNK <= SIZE'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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], lfs_min(CHUNK, SIZE-i))
|
|
=> lfs_min(CHUNK, SIZE-i);
|
|
|
|
// 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, LFS_M_RDWR, 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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of fragments? we need our internal btree traversal
|
|
// API to check this
|
|
//
|
|
lfs_size_t fragments = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many fragments we've seen
|
|
fragments += 1;
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// we disabled block crystallization so this shouldn't
|
|
// happen
|
|
assert(false);
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of fragments?
|
|
assert(fragments == N);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that incremental block-aligned writes always end up as compact blocks
|
|
[cases.test_fwrite_incr_litmus_blocks]
|
|
defines.N = [0, 1, 2, 3, 4]
|
|
defines.SIZE = 'N*BLOCK_SIZE'
|
|
defines.CHUNK = [32, 8, 1]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = 'CHUNK <= SIZE'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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], lfs_min(CHUNK, SIZE-i))
|
|
=> lfs_min(CHUNK, SIZE-i);
|
|
|
|
// 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, LFS_M_RDWR, 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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of branches/blocks? we need our internal btree
|
|
// traversal API to check this
|
|
//
|
|
lfs_block_t blocks = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// if block crystallization is working we shouldn't be
|
|
// left with any inlined data fragments
|
|
assert(false);
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many data blocks we've seen
|
|
blocks += 1;
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of blocks?
|
|
assert(blocks == N);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# overwrite files
|
|
# TODO this is too slow right now, but should speed up with better
|
|
# write strategies
|
|
[cases.test_fwrite_overwrite]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.CHUNK = [32, 8, 1]
|
|
# MASK&0x1 => first chunk
|
|
# MASK&0x2 => middle chunk
|
|
# MASK&0x4 => last chunk
|
|
defines.MASK = [0, 1, 2, 3, 4, 5, 6, 7]
|
|
# ORDER=0 => in-order
|
|
# ORDER=1 => reversed
|
|
defines.ORDER = [0, 1]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 = 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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
|
|
// write first chunk?
|
|
if (MASK & 0x1) {
|
|
if (ORDER == 0) {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK;
|
|
} else {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK;
|
|
}
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
|
|
// write second chunk?
|
|
if (MASK & 0x2) {
|
|
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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
|
|
// write third chunk?
|
|
if (MASK & 0x4) {
|
|
if (ORDER == 0) {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK;
|
|
} else {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK;
|
|
}
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// 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_fwrite_holes]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.CHUNK = [32, 8, 1]
|
|
# MASK&0x1 => first chunk
|
|
# MASK&0x2 => middle chunk
|
|
# MASK&0x4 => last chunk
|
|
defines.MASK = [0, 1, 2, 3, 4, 5, 6, 7]
|
|
# ORDER=0 => in-order
|
|
# ORDER=1 => reversed
|
|
defines.ORDER = [0, 1]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 = 42;
|
|
memset(sim, 0, SIZE);
|
|
// we may not write the entire file
|
|
lfs_off_t size
|
|
= (MASK & ((ORDER == 0) ? 0x4 : 0x1)) ? SIZE
|
|
: (MASK & ((ORDER == 0) ? 0x2 : 0x2)) ? SIZE/2 + (CHUNK+2-1)/2
|
|
: (MASK & ((ORDER == 0) ? 0x1 : 0x4)) ? 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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
|
|
// write first chunk?
|
|
if (MASK & 0x1) {
|
|
if (ORDER == 0) {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK;
|
|
} else {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK;
|
|
}
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
|
|
// write second chunk?
|
|
if (MASK & 0x2) {
|
|
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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
|
|
// write third chunk?
|
|
if (MASK & 0x4) {
|
|
if (ORDER == 0) {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK;
|
|
} else {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK;
|
|
}
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// 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_fwrite_truncate]
|
|
defines.FROM = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.TO = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
# this just saves testing time
|
|
'FROM / FRAGMENT_SIZE <= 4096',
|
|
'TO / FRAGMENT_SIZE <= 4096',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[lfs_max(FROM,TO)];
|
|
memset(sim, 0, lfs_max(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, LFS_M_RDWR, 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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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);
|
|
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 == 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_fwrite_truncate_truncate]
|
|
defines.FROM = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.AND = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.TO = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
# this just saves testing time
|
|
'FROM / FRAGMENT_SIZE <= 4096',
|
|
'AND / FRAGMENT_SIZE <= 4096',
|
|
'TO / FRAGMENT_SIZE <= 4096',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[lfs_max(FROM,lfs_max(AND,TO))];
|
|
memset(sim, 0, lfs_max(FROM,lfs_max(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, LFS_M_RDWR, 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, LFS_M_RDWR, 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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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);
|
|
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 == 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_fwrite_fruncate]
|
|
defines.FROM = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.TO = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
# this just saves testing time
|
|
'FROM / FRAGMENT_SIZE <= 4096',
|
|
'TO / FRAGMENT_SIZE <= 4096',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[lfs_max(FROM,TO)];
|
|
memset(sim, 0, lfs_max(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, LFS_M_RDWR, 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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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);
|
|
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 == 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_fwrite_fruncate_fruncate]
|
|
defines.FROM = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.AND = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.TO = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
# this just saves testing time
|
|
'FROM / FRAGMENT_SIZE <= 4096',
|
|
'AND / FRAGMENT_SIZE <= 4096',
|
|
'TO / FRAGMENT_SIZE <= 4096',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[lfs_max(FROM,lfs_max(AND,TO))];
|
|
memset(sim, 0, lfs_max(FROM,lfs_max(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, LFS_M_RDWR, 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, LFS_M_RDWR, 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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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);
|
|
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 == 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;
|
|
'''
|
|
|
|
# truncate should not affect pos
|
|
[cases.test_fwrite_truncate_pos]
|
|
defines.POS = ['1', 'SIZE/2', 'SIZE-1', '2*SIZE']
|
|
defines.SIZE = '4*BLOCK_SIZE'
|
|
defines.SYNC = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// seek
|
|
lfsr_file_seek(&lfs, &file, POS, LFS_SEEK_SET) => POS;
|
|
|
|
// truncate
|
|
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
|
|
|
|
// should not affect pos
|
|
lfsr_file_tell(&lfs, &file) => POS;
|
|
lfsr_file_size(&lfs, &file) => SIZE;
|
|
|
|
// truncate
|
|
lfsr_file_truncate(&lfs, &file, 1) => 0;
|
|
|
|
// should not affect pos
|
|
lfsr_file_tell(&lfs, &file) => POS;
|
|
lfsr_file_size(&lfs, &file) => 1;
|
|
|
|
// truncate
|
|
lfsr_file_truncate(&lfs, &file, SIZE-1) => 0;
|
|
|
|
// should not affect pos
|
|
lfsr_file_tell(&lfs, &file) => POS;
|
|
lfsr_file_size(&lfs, &file) => SIZE-1;
|
|
|
|
// truncate
|
|
lfsr_file_truncate(&lfs, &file, 0) => 0;
|
|
|
|
// should not affect pos
|
|
lfsr_file_tell(&lfs, &file) => POS;
|
|
lfsr_file_size(&lfs, &file) => 0;
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# fruncate should update pos relative to end
|
|
[cases.test_fwrite_fruncate_pos]
|
|
defines.POS = ['1', 'SIZE/2', 'SIZE-1', '2*SIZE']
|
|
defines.SIZE = '4*BLOCK_SIZE'
|
|
defines.SYNC = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// seek
|
|
lfsr_file_seek(&lfs, &file, POS, LFS_SEEK_SET) => POS;
|
|
|
|
// fruncate
|
|
lfsr_file_fruncate(&lfs, &file, SIZE) => 0;
|
|
|
|
// should update pos
|
|
lfsr_file_tell(&lfs, &file) => POS + SIZE;
|
|
lfsr_file_size(&lfs, &file) => SIZE;
|
|
|
|
// seek
|
|
lfsr_file_seek(&lfs, &file, POS, LFS_SEEK_SET) => POS;
|
|
|
|
// fruncate
|
|
lfsr_file_fruncate(&lfs, &file, 1) => 0;
|
|
|
|
// should update pos
|
|
lfsr_file_tell(&lfs, &file) => lfs_smax(POS - (SIZE-1), 0);
|
|
lfsr_file_size(&lfs, &file) => 1;
|
|
|
|
// seek
|
|
lfsr_file_seek(&lfs, &file, POS, LFS_SEEK_SET) => POS;
|
|
|
|
// fruncate
|
|
lfsr_file_fruncate(&lfs, &file, SIZE-1) => 0;
|
|
|
|
// should update pos
|
|
lfsr_file_tell(&lfs, &file) => POS + (SIZE-2);
|
|
lfsr_file_size(&lfs, &file) => SIZE-1;
|
|
|
|
// seek
|
|
lfsr_file_seek(&lfs, &file, POS, LFS_SEEK_SET) => POS;
|
|
|
|
// fruncate
|
|
lfsr_file_fruncate(&lfs, &file, 0) => 0;
|
|
|
|
// should update pos
|
|
lfsr_file_tell(&lfs, &file) => lfs_smax(POS - (SIZE-1), 0);
|
|
lfsr_file_size(&lfs, &file) => 0;
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that truncating to zero drops the bshrub/btree
|
|
[cases.test_fwrite_truncate_litmus_zero]
|
|
defines.N = [1, 2, 8]
|
|
defines.SIZE = 'N*BLOCK_SIZE'
|
|
defines.SYNC = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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;
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// truncate down to zero, this should drop any bshrub/btree
|
|
lfsr_file_truncate(&lfs, &file, 0) => 0;
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[2];
|
|
memset(rbuf, 0xaa, 2);
|
|
lfsr_file_read(&lfs, &file, rbuf, 2) => 0;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// here's our main test, did the file drop bshrubs/btrees?
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
assert(lfsr_bshrub_isbnull(&file.b));
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that fruncating to zero drops the bshrub/btree
|
|
[cases.test_fwrite_fruncate_litmus_zero]
|
|
defines.N = [1, 2, 8]
|
|
defines.SIZE = 'N*BLOCK_SIZE'
|
|
defines.SYNC = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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;
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// fruncate down to zero, this should drop any bshrub/btree
|
|
lfsr_file_fruncate(&lfs, &file, 0) => 0;
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[2];
|
|
memset(rbuf, 0xaa, 2);
|
|
lfsr_file_read(&lfs, &file, rbuf, 2) => 0;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// here's our main test, did the file drop bshrubs/btrees?
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
assert(lfsr_bshrub_isbnull(&file.b));
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that carving below fragment_thresh breaks blocks into fragments
|
|
[cases.test_fwrite_truncate_litmus_fragment]
|
|
defines.N = [1, 2, 8]
|
|
defines.SIZE = 'N*BLOCK_SIZE'
|
|
# 1 vs multiple fragments are implemented slightly differently
|
|
defines.FRAGMENTS = [1, 2, 3]
|
|
defines.TSIZE = 'FRAGMENTS*FRAGMENT_SIZE'
|
|
defines.SYNC = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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;
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// truncate down to truncate size, this should fragment our blocks
|
|
lfsr_file_truncate(&lfs, &file, TSIZE) => 0;
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 == TSIZE);
|
|
|
|
// 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 == TSIZE);
|
|
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) => TSIZE;
|
|
// try reading
|
|
uint8_t rbuf[2*TSIZE];
|
|
memset(rbuf, 0xaa, 2*TSIZE);
|
|
lfsr_file_read(&lfs, &file, rbuf, 2*TSIZE) => TSIZE;
|
|
assert(memcmp(rbuf, wbuf, TSIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of fragments? we need our internal btree traversal
|
|
// API to check this
|
|
//
|
|
lfs_size_t fragments = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many fragments we've seen
|
|
fragments += 1;
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// all blocks should have been fragmented
|
|
assert(false);
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of fragments?
|
|
assert(fragments == FRAGMENTS);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_fwrite_fruncate_litmus_fragment]
|
|
defines.N = [1, 2, 8]
|
|
defines.SIZE = 'N*BLOCK_SIZE'
|
|
# 1 vs multiple fragments are implemented slightly differently
|
|
defines.FRAGMENTS = [1, 2, 3]
|
|
defines.TSIZE = 'FRAGMENTS*FRAGMENT_SIZE'
|
|
defines.SYNC = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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;
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// fruncate down to fruncate size, this should fragment our blocks
|
|
lfsr_file_fruncate(&lfs, &file, TSIZE) => 0;
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 == TSIZE);
|
|
|
|
// 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 == TSIZE);
|
|
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) => TSIZE;
|
|
// try reading
|
|
uint8_t rbuf[2*TSIZE];
|
|
memset(rbuf, 0xaa, 2*TSIZE);
|
|
lfsr_file_read(&lfs, &file, rbuf, 2*TSIZE) => TSIZE;
|
|
assert(memcmp(rbuf, wbuf+(SIZE-TSIZE), TSIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of fragments? we need our internal btree traversal
|
|
// API to check this
|
|
//
|
|
lfs_size_t fragments = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many fragments we've seen
|
|
fragments += 1;
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// all blocks should have been fragmented
|
|
assert(false);
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of fragments?
|
|
assert(fragments == FRAGMENTS);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# writing any data structure backwards always reveals issues
|
|
[cases.test_fwrite_reversed]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.CHUNK = [32, 8, 1]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
# writing backwards is expected to be a bit slow
|
|
'SIZE <= 4*1024*CHUNK',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[SIZE];
|
|
uint32_t prng = 42;
|
|
if (INIT == 0) {
|
|
// do nothing
|
|
} else if (INIT == 1) {
|
|
for (lfs_size_t i = 0; i < SIZE; i++) {
|
|
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
} else {
|
|
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, LFS_M_RDWR, 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++) {
|
|
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
|
|
lfsr_file_seek(&lfs, &file, SIZE-i-CHUNK, LFS_SEEK_SET) => SIZE-i-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &wbuf[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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 that reversed fragment-aligned writes are optimal
|
|
[cases.test_fwrite_reversed_litmus_fragments]
|
|
defines.N = [0, 1, 2, 3, 4]
|
|
defines.SIZE = 'N*FRAGMENT_SIZE'
|
|
defines.CHUNK = [32, 8, 1]
|
|
# force a btree node
|
|
defines.INLINE_SIZE = 0
|
|
defines.CRYSTAL_THRESH = -1
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# writing backwards is expected to be a bit slow
|
|
'SIZE <= 4*1024*CHUNK',
|
|
]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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) {
|
|
lfs_off_t chunk_ = lfs_min(CHUNK, SIZE-i);
|
|
lfs_off_t i_ = SIZE-i-chunk_;
|
|
lfsr_file_seek(&lfs, &file, i_, LFS_SEEK_SET) => i_;
|
|
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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of fragments? we need our internal btree traversal
|
|
// API to check this
|
|
//
|
|
lfs_size_t fragments = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many fragments we've seen
|
|
fragments += 1;
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// we disabled block crystallization so this shouldn't
|
|
// happen
|
|
assert(false);
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of fragments?
|
|
assert(fragments == N);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that reversed block-aligned writes always end up as compact blocks
|
|
[cases.test_fwrite_reversed_litmus_blocks]
|
|
defines.N = [0, 1, 2, 3, 4]
|
|
defines.SIZE = 'N*BLOCK_SIZE'
|
|
defines.CHUNK = [32, 8, 1]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# writing backwards is expected to be a bit slow
|
|
'SIZE <= 4*1024*CHUNK',
|
|
]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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) {
|
|
lfs_off_t chunk_ = lfs_min(CHUNK, SIZE-i);
|
|
lfs_off_t i_ = SIZE-i-chunk_;
|
|
lfsr_file_seek(&lfs, &file, i_, LFS_SEEK_SET) => i_;
|
|
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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of branches/blocks? we need our internal btree
|
|
// traversal API to check this
|
|
//
|
|
lfs_block_t blocks = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// if block crystallization is working we shouldn't be
|
|
// left with any inlined data fragments
|
|
assert(false);
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many data blocks we've seen
|
|
blocks += 1;
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of blocks?
|
|
assert(blocks == N);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# with lfsr_file_fruncate, we can write to a file in true reversed order
|
|
[cases.test_fwrite_freversed]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.CHUNK = [32, 8, 1]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
# writing backwards is expected to be a bit slow
|
|
'SIZE <= 4*1024*CHUNK',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[SIZE];
|
|
uint32_t prng = 42;
|
|
|
|
// 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, LFS_M_RDWR, 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++) {
|
|
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
|
|
lfs_off_t pos = lfsr_file_tell(&lfs, &file);
|
|
lfsr_file_fruncate(&lfs, &file, i+CHUNK) => 0;
|
|
// pos shouldn't move when we fruncate
|
|
lfsr_file_tell(&lfs, &file) => pos + CHUNK;
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &wbuf[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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 that reversed fragment-aligned writes are optimal
|
|
[cases.test_fwrite_freversed_litmus_fragments]
|
|
defines.N = [0, 1, 2, 3, 4]
|
|
defines.SIZE = 'N*FRAGMENT_SIZE'
|
|
defines.CHUNK = [32, 8, 1]
|
|
# force a btree node
|
|
defines.INLINE_SIZE = 0
|
|
defines.CRYSTAL_THRESH = -1
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# writing backwards is expected to be a bit slow
|
|
'SIZE <= 4*1024*CHUNK',
|
|
]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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) {
|
|
lfs_off_t chunk_ = lfs_min(CHUNK, SIZE-i);
|
|
lfs_off_t i_ = SIZE-i-chunk_;
|
|
lfs_off_t pos = lfsr_file_tell(&lfs, &file);
|
|
lfsr_file_fruncate(&lfs, &file, i+chunk_) => 0;
|
|
// pos shouldn't move when we fruncate
|
|
lfsr_file_tell(&lfs, &file) => pos + chunk_;
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of fragments? we need our internal btree traversal
|
|
// API to check this
|
|
//
|
|
lfs_size_t fragments = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many fragments we've seen
|
|
fragments += 1;
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// we disabled block crystallization so this shouldn't
|
|
// happen
|
|
assert(false);
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of fragments?
|
|
assert(fragments == N);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that reversed block-aligned writes always end up as compact blocks
|
|
[cases.test_fwrite_freversed_litmus_blocks]
|
|
defines.N = [0, 1, 2, 3, 4]
|
|
defines.SIZE = 'N*BLOCK_SIZE'
|
|
defines.CHUNK = [32, 8, 1]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# writing backwards is expected to be a bit slow
|
|
'SIZE <= 4*1024*CHUNK',
|
|
]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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[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) {
|
|
lfs_off_t chunk_ = lfs_min(CHUNK, SIZE-i);
|
|
lfs_off_t i_ = SIZE-i-chunk_;
|
|
lfs_off_t pos = lfsr_file_tell(&lfs, &file);
|
|
lfsr_file_fruncate(&lfs, &file, i+chunk_) => 0;
|
|
// pos shouldn't move when we fruncate
|
|
lfsr_file_tell(&lfs, &file) => pos + chunk_;
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
// here's our main test, do we end up with the expected
|
|
// number of branches/blocks? we need our internal btree
|
|
// traversal API to check this
|
|
//
|
|
lfs_block_t blocks = 0;
|
|
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
lfsr_btraversal_t bt;
|
|
lfsr_btraversal_init(&bt);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_bid_t bid;
|
|
lfsr_tag_t tag;
|
|
lfsr_bptr_t bptr;
|
|
int err = lfsr_file_traverse(&lfs, &file, &bt,
|
|
&bid, &tag, &bptr);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFSR_TAG_BRANCH) {
|
|
lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer;
|
|
printf("traversal: %d 0x%x btree 0x%x.%x\n",
|
|
bid,
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else if (tag == LFSR_TAG_DATA) {
|
|
printf("traversal: %d 0x%x data %d\n",
|
|
bid,
|
|
tag,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// if block crystallization is working we shouldn't be
|
|
// left with any inlined data fragments
|
|
assert(false);
|
|
|
|
} else if (tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: %d 0x%x block 0x%x.%x %d\n",
|
|
bid,
|
|
tag,
|
|
bptr.data.u.disk.block,
|
|
bptr.data.u.disk.off,
|
|
lfsr_data_size(bptr.data));
|
|
|
|
// keep track of how many data blocks we've seen
|
|
blocks += 1;
|
|
|
|
} else {
|
|
// well this shouldn't happen
|
|
printf("traversal: %d 0x%x\n",
|
|
bid,
|
|
tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// correct number of blocks?
|
|
assert(blocks == N);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# these are like the overwrite/hole tests, but with enough rewrites to
|
|
# trigger compaction
|
|
[cases.test_fwrite_overwrite_compaction]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.CHUNK = [32, 8, 1]
|
|
# MASK&0x1 => first chunk
|
|
# MASK&0x2 => middle chunk
|
|
# MASK&0x4 => last chunk
|
|
defines.MASK = [0, 1, 2, 3, 4, 5, 6, 7]
|
|
# ORDER=0 => in-order
|
|
# ORDER=1 => reversed
|
|
defines.ORDER = [0, 1]
|
|
# writing this many times guarantees a compaction
|
|
defines.WRITES = '2*(BLOCK_SIZE/PROG_SIZE)'
|
|
# TODO is setting PROG_SIZE here reasonable?
|
|
defines.PROG_SIZE = 64
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 = 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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
|
|
// write first chunk?
|
|
if (MASK & 0x1) {
|
|
for (lfs_size_t w = 0; w < WRITES; w++) {
|
|
if (ORDER == 0) {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK;
|
|
} else {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK;
|
|
}
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write second chunk?
|
|
if (MASK & 0x2) {
|
|
for (lfs_size_t w = 0; w < WRITES; w++) {
|
|
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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write third chunk?
|
|
if (MASK & 0x4) {
|
|
for (lfs_size_t w = 0; w < WRITES; w++) {
|
|
if (ORDER == 0) {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK;
|
|
} else {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK;
|
|
}
|
|
}
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// does our file match our simulation?
|
|
assert(memcmp(rbuf, sim, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_fwrite_hole_compaction]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.CHUNK = [32, 8, 1]
|
|
# MASK&0x1 => first chunk
|
|
# MASK&0x2 => middle chunk
|
|
# MASK&0x4 => last chunk
|
|
defines.MASK = [0, 1, 2, 3, 4, 5, 6, 7]
|
|
# ORDER=0 => in-order
|
|
# ORDER=1 => reversed
|
|
defines.ORDER = [0, 1]
|
|
# writing this many times guarantees a compaction
|
|
defines.WRITES = '2*(BLOCK_SIZE/PROG_SIZE)'
|
|
# TODO is setting PROG_SIZE here reasonable?
|
|
defines.PROG_SIZE = 64
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 = 42;
|
|
memset(sim, 0, SIZE);
|
|
// we may not write the entire file
|
|
lfs_off_t size
|
|
= (MASK & ((ORDER == 0) ? 0x4 : 0x1)) ? SIZE
|
|
: (MASK & ((ORDER == 0) ? 0x2 : 0x2)) ? SIZE/2 + (CHUNK+2-1)/2
|
|
: (MASK & ((ORDER == 0) ? 0x1 : 0x4)) ? 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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
|
|
// write first chunk?
|
|
if (MASK & 0x1) {
|
|
for (lfs_size_t w = 0; w < WRITES; w++) {
|
|
if (ORDER == 0) {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK;
|
|
} else {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK;
|
|
}
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write second chunk?
|
|
if (MASK & 0x2) {
|
|
for (lfs_size_t w = 0; w < WRITES; w++) {
|
|
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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write third chunk?
|
|
if (MASK & 0x4) {
|
|
for (lfs_size_t w = 0; w < WRITES; w++) {
|
|
if (ORDER == 0) {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK;
|
|
lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK;
|
|
} else {
|
|
for (lfs_size_t i = 0; i < CHUNK; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
|
lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK;
|
|
}
|
|
}
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// 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_fwrite_fuzz_aligned]
|
|
defines.N = 20
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.CHUNK = [32, 8, 1]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
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, LFS_M_RDWR, 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_max(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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// 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_fwrite_fuzz_unaligned]
|
|
defines.N = 20
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# chunk is more an upper limit here
|
|
defines.CHUNK = [64, 16]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
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, LFS_M_RDWR, 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_min(
|
|
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
|
SIZE - off);
|
|
|
|
// update sim
|
|
for (lfs_size_t j = 0; j < chunk; j++) {
|
|
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
size = lfs_max(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, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0;
|
|
}
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// 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_fwrite_r_seek]
|
|
defines.N = 20
|
|
defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END']
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# chunk is more an upper limit here
|
|
defines.CHUNK = [64, 16]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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_min(
|
|
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
|
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_fwrite_w_seek]
|
|
defines.N = 10
|
|
defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END']
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# chunk is more an upper limit here
|
|
defines.CHUNK = [64, 16]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.SYNC = [false, true]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
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_min(
|
|
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
|
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);
|
|
}
|
|
size = lfs_max(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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// 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_fwrite_rw_seek]
|
|
defines.N = 10
|
|
defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END']
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# chunk is more an upper limit here
|
|
defines.CHUNK = [64, 16]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.SYNC = [false, true]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
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_min(
|
|
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
|
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);
|
|
}
|
|
size = lfs_max(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_min(
|
|
chunk,
|
|
size - lfs_min(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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// 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
|
|
|
|
# test that seeking to a negative offset errors
|
|
[cases.test_fwrite_seek_negative]
|
|
defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END']
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.MODE = ['LFS_O_RDONLY', 'LFS_O_WRONLY', 'LFS_O_RDWR']
|
|
if = [
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// does our file match our simulation?
|
|
assert(memcmp(rbuf, sim, size) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that write overflow errors
|
|
[cases.test_fwrite_fbig]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.MODE = ['LFS_O_WRONLY', 'LFS_O_RDWR']
|
|
if = [
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
memset(sim, 0, SIZE);
|
|
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
|
|
size = SIZE;
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// seek to near the file limit
|
|
lfsr_file_open(&lfs, &file, "hello", MODE) => 0;
|
|
lfsr_file_seek(&lfs, &file, LFS_FILE_MAX-(SIZE/2), LFS_SEEK_SET)
|
|
=> LFS_FILE_MAX-(SIZE/2);
|
|
// try to write past the file limit, this should fail
|
|
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) => LFS_ERR_FBIG;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// does our file match our simulation?
|
|
assert(memcmp(rbuf, sim, size) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that truncate overflow errors
|
|
[cases.test_fwrite_truncate_fbig]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.MODE = ['LFS_O_WRONLY', 'LFS_O_RDWR']
|
|
if = [
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
memset(sim, 0, SIZE);
|
|
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
|
|
size = SIZE;
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// try to truncate the file past the file limit, this should fail
|
|
lfsr_file_open(&lfs, &file, "hello", MODE) => 0;
|
|
lfsr_file_truncate(&lfs, &file, LFS_FILE_MAX+(SIZE/2)) => LFS_ERR_FBIG;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// does our file match our simulation?
|
|
assert(memcmp(rbuf, sim, size) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that fruncate overflow errors
|
|
[cases.test_fwrite_fruncate_fbig]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.MODE = ['LFS_O_WRONLY', 'LFS_O_RDWR']
|
|
if = [
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
memset(sim, 0, SIZE);
|
|
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
|
|
size = SIZE;
|
|
}
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// try to truncate the file past the file limit, this should fail
|
|
lfsr_file_open(&lfs, &file, "hello", MODE) => 0;
|
|
lfsr_file_fruncate(&lfs, &file, LFS_FILE_MAX+(SIZE/2)) => LFS_ERR_FBIG;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// 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_fwrite_rwtf_fuzz]
|
|
defines.N = 20
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# chunk is more an upper limit here
|
|
defines.CHUNK = [64, 16]
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.SYNC = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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 {
|
|
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, LFS_M_RDWR, 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_min(
|
|
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
|
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);
|
|
}
|
|
size = lfs_max(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, LFS_M_RDWR, 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_min(
|
|
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
|
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_min(
|
|
chunk,
|
|
size - lfs_min(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;
|
|
|
|
// fruncating?
|
|
} 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;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
// does our file match our simulation?
|
|
assert(memcmp(rbuf, sim, size) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|