Added a couple more fwrite litmus tests
- 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%)
This commit is contained in:
@@ -11989,7 +11989,8 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
|
||||
|
||||
// within our tree? find left crystal neighbor
|
||||
if (pos > 0
|
||||
&& lfs->cfg->crystal_thresh > 0
|
||||
// if crystal_thresh is 0 or -1, we can skip these
|
||||
&& (lfs_soff_t)lfs->cfg->crystal_thresh > 0
|
||||
&& (lfs_soff_t)(pos - (lfs->cfg->crystal_thresh-1))
|
||||
< (lfs_soff_t)file->b.shrub.weight
|
||||
&& file->b.shrub.weight > 0
|
||||
@@ -12010,6 +12011,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
|
||||
// obvious hole between our own crystal and our neighbor,
|
||||
// include as a part of our crystal
|
||||
if (!lfsr_bptr_isbptr(&bptr)
|
||||
&& lfsr_data_size(bptr.data) > 0
|
||||
// hole? holes can be quite large and shouldn't trigger
|
||||
// crystallization
|
||||
&& (lfs_soff_t)(bid-(weight-1)+lfsr_data_size(bptr.data))
|
||||
@@ -12066,7 +12068,8 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
|
||||
|
||||
// if right crystal neighbor is a fragment, include as a part
|
||||
// of our crystal
|
||||
if (!lfsr_bptr_isbptr(&bptr)) {
|
||||
if (!lfsr_bptr_isbptr(&bptr)
|
||||
&& lfsr_data_size(bptr.data) > 0) {
|
||||
crystal_end = lfs_max(
|
||||
bid-(weight-1)+lfsr_data_size(bptr.data),
|
||||
crystal_end);
|
||||
@@ -13091,6 +13094,12 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
|
||||
size - size_,
|
||||
file->cache.pos);
|
||||
|
||||
// fruncate _does_ update pos, to keep the same pos relative to end
|
||||
// of file, though we can't let pos go negative
|
||||
file->pos -= lfs_smin(
|
||||
size - size_,
|
||||
file->pos);
|
||||
|
||||
// sync if requested
|
||||
if (lfsr_o_issync(file->b.o.flags)) {
|
||||
err = lfsr_file_sync(lfs, file);
|
||||
|
||||
+889
-12
@@ -1690,6 +1690,117 @@ code = '''
|
||||
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]
|
||||
@@ -2178,18 +2289,16 @@ code = '''
|
||||
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];
|
||||
uint8_t wbuf[SIZE];
|
||||
uint32_t prng = 42;
|
||||
if (INIT == 0) {
|
||||
memset(sim, 0, SIZE);
|
||||
// do nothing
|
||||
} else if (INIT == 1) {
|
||||
for (lfs_size_t i = 0; i < SIZE; i++) {
|
||||
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
} else {
|
||||
memset(sim, 0, SIZE);
|
||||
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
|
||||
}
|
||||
|
||||
@@ -2207,12 +2316,12 @@ code = '''
|
||||
}
|
||||
|
||||
// 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) {
|
||||
for (lfs_size_t j = 0; j < CHUNK; j++) {
|
||||
sim[SIZE-i-CHUNK+j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_seek(&lfs, &file, SIZE-i-CHUNK, LFS_SEEK_SET) => SIZE-i-CHUNK;
|
||||
lfsr_file_write(&lfs, &file, &sim[SIZE-i-CHUNK], CHUNK) => CHUNK;
|
||||
lfsr_file_write(&lfs, &file, &wbuf[SIZE-i-CHUNK], CHUNK) => CHUNK;
|
||||
|
||||
// sync?
|
||||
if (SYNC) {
|
||||
@@ -2269,14 +2378,782 @@ code = '''
|
||||
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);
|
||||
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]
|
||||
|
||||
Reference in New Issue
Block a user