Locked down out-of-order writes, more tests
The main test additions are the test_powerloss tests, intended to be
high-level tests over difficult/weird powerloss environments (such as
out-of-order writes!):
- test_powerloss_dir_many - 2242 pls
- test_powerloss_file_many - 8856 pls
- test_powerloss_file_pl_fuzz - 384508 pls
- test_powerloss_filedir_pl_fuzz - 268339 pls
But there was also a bunch of other test movement in the late-stage/
high-level tests. I'm trying to keep the core of these tests somewhat
consistent so we have a nice template to extend for future testing, in
case we want to test other environmentalish concerns, but not all of
these tests make sense in all of these contexts:
badblocks powerloss relocations exhaustion
dir_many y y y
dir_fuzz y y y
file_many y y y
file_fuzz y y y
fwrite_fuzz y y
orphanzombie_fuzz y y y
orphanzombiedir_fuzz y y y
file_pl_fuzz y y
filedir_pl_fuzz y y
Why not:
- dir/file_many+exhaustion? - Needs to be unbounded
- dir/file_fuzz+powerloss? - Takes O(n^2)
- fwrite_fuzz+powerloss? - Takes O(n^2)
- fwrite_fuzz+relocations? - Doesn't really test anything
- orphanzombie*_fuzz+powerloss? - Powerloss kills zombies
- file*_pl_fuzz+badblocks? - PL + Badblocks currently incompactible
- file*_pl_fuzz+exhaustion? - PL + Badblocks currently incompactible
---
Of course, in order to actually get out-of-order write testing working,
we need to implement out-of-order write syncing.
Fortunately this was a simple exercise in placing lfsr_bd_sync calls
before any mdir commits where we may have unsynced data:
- in lfsr_file_sync, to sync any pending file data
- in lfsr_mdir_commit, to sync any mroot/mtree changes
We also call lfsr_bd_sync _after_ mdir commits in case users expect to
sequence any filesystem-external operations such as network, UI, etc. In
theory this could be optional, but no users have really requested it
yet, so leave that for a potential future improvement:
- in lfsr_mdir_commit
- in lfsr_formatinited (really just because we don't go through
lfsr_mdir_commit)
Note that lfsr_rbyd_commit has been relaxed in the scheme. It only
flushes caches, and does _not_ call lfsr_bd_sync. This is useful for
allowing multiple B-tree nodes to be written out-of-order, also long as
the whole thing is synchronized before any mdir commit.
All of these lfsr_bd_sync calls add a bit of code, but not really an
amount to care about:
code stack
before: 33678 2600
after: 33766 (+0.3%) 2600 (+0.0%)
This commit is contained in:
@@ -3461,8 +3461,8 @@ static int lfsr_rbyd_appendcksum(lfs_t *lfs, lfsr_rbyd_t *rbyd) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// flush our caches, finalizing the commit on-disk
|
||||
err = lfsr_bd_sync(lfs);
|
||||
// flush any pending progs
|
||||
err = lfsr_bd_flush(lfs, NULL, false);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -7115,6 +7115,13 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
}
|
||||
}
|
||||
|
||||
// make sure mtree/mroot changes are on-disk before committing
|
||||
// metadata
|
||||
err = lfsr_bd_sync(lfs);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// commit new mtree into our mroot
|
||||
//
|
||||
// note end_rid=0 here will delete any files leftover from a split
|
||||
@@ -7157,6 +7164,13 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
|
||||
mrootchild = mrootparent_;
|
||||
|
||||
// make sure mtree/mroot changes are on-disk before committing
|
||||
// metadata
|
||||
err = lfsr_bd_sync(lfs);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// commit mrootchild
|
||||
uint8_t mrootchild_buf[LFSR_MPTR_DSIZE];
|
||||
err = lfsr_mdir_commit_(lfs, &mrootparent_, -1, -1, NULL,
|
||||
@@ -7186,6 +7200,13 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
mrootchild.rbyd.blocks[0], mrootchild.rbyd.blocks[1],
|
||||
mrootchild_.rbyd.blocks[0], mrootchild_.rbyd.blocks[1]);
|
||||
|
||||
// make sure mtree/mroot changes are on-disk before committing
|
||||
// metadata
|
||||
err = lfsr_bd_sync(lfs);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// commit the new mroot anchor
|
||||
lfsr_mdir_t mrootanchor_;
|
||||
err = lfsr_mdir_swap__(lfs, &mrootanchor_, &mrootchild, true);
|
||||
@@ -7215,6 +7236,12 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
// gstate must have been committed by a lower-level function at this point
|
||||
LFS_ASSERT(lfsr_gdelta_iszero(lfs->grm_d, LFSR_GRM_DSIZE));
|
||||
|
||||
// sync on-disk state
|
||||
err = lfsr_bd_sync(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// success? update in-device state, we must not error at this point
|
||||
|
||||
// toss our cksum into the filesystem seed for pseudorandom numbers
|
||||
@@ -8672,8 +8699,14 @@ static int lfsr_formatinited(lfs_t *lfs) {
|
||||
}
|
||||
}
|
||||
|
||||
// sync on-disk state
|
||||
int err = lfsr_bd_sync(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// test that mount works with our formatted disk
|
||||
int err = lfsr_mountinited(lfs);
|
||||
err = lfsr_mountinited(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -11681,7 +11714,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
// checkpoint the allocator again
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
|
||||
// commit our file's metadata
|
||||
// commit any changes to our file's metadata
|
||||
lfsr_attr_t attrs[2];
|
||||
lfs_size_t attr_count = 0;
|
||||
lfsr_data_t name_data;
|
||||
@@ -11728,6 +11761,13 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
LFS_UNREACHABLE();
|
||||
}
|
||||
|
||||
// make sure data is on-disk before committing metadata
|
||||
err = lfsr_bd_sync(lfs);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// commit!
|
||||
LFS_ASSERT(attr_count <= sizeof(attrs)/sizeof(lfsr_attr_t));
|
||||
|
||||
err = lfsr_mdir_commit(lfs, &file->o.mdir,
|
||||
|
||||
+3248
-1220
File diff suppressed because it is too large
Load Diff
+183
-2
@@ -2,6 +2,7 @@
|
||||
after = [
|
||||
'test_dirs',
|
||||
'test_files',
|
||||
'test_fwrite',
|
||||
'test_forphans',
|
||||
'test_alloc',
|
||||
'test_badblocks',
|
||||
@@ -489,7 +490,187 @@ code = '''
|
||||
assert(run_ops[1]*110/100 > 2*run_ops[0]);
|
||||
'''
|
||||
|
||||
# just more things that could go wrong
|
||||
# with more complex file writes
|
||||
[cases.test_exhaustion_fwrite_fuzz]
|
||||
defines.ERASE_CYCLES = 10
|
||||
defines.BLOCK_RECYCLES = 4
|
||||
defines.BADBLOCK_BEHAVIOR = [
|
||||
'LFS_EMUBD_BADBLOCK_PROGERROR',
|
||||
'LFS_EMUBD_BADBLOCK_ERASEERROR',
|
||||
'LFS_EMUBD_BADBLOCK_READERROR',
|
||||
'LFS_EMUBD_BADBLOCK_PROGNOOP',
|
||||
'LFS_EMUBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
# we need prog checking to detect read errors
|
||||
defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
# chunk is more an upper limit here
|
||||
defines.CHUNK = 64
|
||||
# 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 = 42
|
||||
fuzz = 'SEED'
|
||||
if = [
|
||||
'CHUNK <= SIZE',
|
||||
# this just saves testing time
|
||||
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
||||
]
|
||||
code = '''
|
||||
// run our test twice, once with 1/2 the storage, once with 2/2 the
|
||||
// storage, and compare how many operations we were able to perform
|
||||
// before filesystem death
|
||||
uint32_t run_bc[2] = {BLOCK_COUNT/2, BLOCK_COUNT};
|
||||
uint32_t run_ops[2] = {0, 0};
|
||||
|
||||
for (int run = 0; run < 2; run++) {
|
||||
// clear any wear from the previous run
|
||||
for (lfs_block_t i = 0; i < BLOCK_COUNT; i++) {
|
||||
lfs_emubd_setwear(CFG, i, 0) => 0;
|
||||
}
|
||||
|
||||
// configure the filesystem size
|
||||
struct lfs_config cfg = *CFG;
|
||||
cfg.block_count = run_bc[run];
|
||||
|
||||
// run the test
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, &cfg) => 0;
|
||||
lfsr_mount(&lfs, &cfg) => 0;
|
||||
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "hello",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
// simulate our file in ram
|
||||
uint8_t sim[SIZE];
|
||||
lfs_off_t size;
|
||||
uint32_t prng = SEED;
|
||||
if (INIT == 0) {
|
||||
memset(sim, 0, SIZE);
|
||||
size = 0;
|
||||
} else if (INIT == 1) {
|
||||
for (lfs_size_t i = 0; i < SIZE; i++) {
|
||||
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
|
||||
size = SIZE;
|
||||
} else {
|
||||
memset(sim, 0, SIZE);
|
||||
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
|
||||
size = SIZE;
|
||||
}
|
||||
|
||||
// sync?
|
||||
if (SYNC) {
|
||||
lfsr_file_sync(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
for (;; run_ops[run]++) {
|
||||
// choose a random location
|
||||
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
|
||||
// and a random size, up to the chunk size
|
||||
lfs_size_t chunk = lfs_min32(
|
||||
(TEST_PRNG(&prng) % (CHUNK+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_max32(size, off+chunk);
|
||||
|
||||
// update file
|
||||
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
lfs_ssize_t d = lfsr_file_write(&lfs, &file, &sim[off], chunk);
|
||||
assert(d == (lfs_ssize_t)chunk || d == LFS_ERR_NOSPC);
|
||||
if (d == LFS_ERR_NOSPC) {
|
||||
goto dead;
|
||||
}
|
||||
|
||||
// sync?
|
||||
if (SYNC) {
|
||||
int err = lfsr_file_sync(&lfs, &file);
|
||||
assert(!err || err == LFS_ERR_NOSPC);
|
||||
if (err == LFS_ERR_NOSPC) {
|
||||
goto dead;
|
||||
}
|
||||
}
|
||||
|
||||
// check our simulation every power-of-2 ops
|
||||
if (lfs_popc(run_ops[run]) == 1 && SYNC) {
|
||||
// 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_t 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;
|
||||
}
|
||||
}
|
||||
|
||||
dead:;
|
||||
// clean up sim/lfs
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// print how many ops
|
||||
printf("run %d, %dx%d, %d ec: %d ops\n",
|
||||
run,
|
||||
(int)BLOCK_SIZE,
|
||||
run_bc[run],
|
||||
(int)ERASE_CYCLES,
|
||||
run_ops[run]);
|
||||
}
|
||||
|
||||
// check that we increased the liftime by ~2x, with ~10% error
|
||||
printf("lifetime: %d -> %d (x%.2f)\n",
|
||||
run_ops[0],
|
||||
run_ops[1],
|
||||
(double)run_ops[1] / (double)run_ops[0]);
|
||||
assert(run_ops[1]*110/100 > 2*run_ops[0]);
|
||||
'''
|
||||
|
||||
# with orphans, zombies, etc
|
||||
[cases.test_exhaustion_orphanzombie_fuzz]
|
||||
defines.ERASE_CYCLES = 10
|
||||
defines.BLOCK_RECYCLES = 4
|
||||
@@ -902,7 +1083,7 @@ code = '''
|
||||
assert(run_ops[1]*110/100 > 2*run_ops[0]);
|
||||
'''
|
||||
|
||||
# just more things that could go wrong
|
||||
# with orphans, zombies, dirs, etc
|
||||
[cases.test_exhaustion_orphanzombiedir_fuzz]
|
||||
defines.ERASE_CYCLES = 10
|
||||
defines.BLOCK_RECYCLES = 4
|
||||
|
||||
@@ -2869,6 +2869,10 @@ code = '''
|
||||
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
// all data should be lowercase ascii
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
assert(rbuf[j] >= 'a' && rbuf[j] <= 'z');
|
||||
}
|
||||
// sum should be equal to 'a' mod 26
|
||||
uint8_t ck = 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
|
||||
+15
-23
@@ -1676,7 +1676,7 @@ defines.SIZE = [
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
# chunk is more an upper limit here
|
||||
defines.CHUNK = [32, 8]
|
||||
defines.CHUNK = [64, 16]
|
||||
# INIT=0 => no init
|
||||
# INIT=1 => fill with data
|
||||
# INIT=2 => truncate to size
|
||||
@@ -1736,16 +1736,14 @@ code = '''
|
||||
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
|
||||
// and a random size, up to the chunk size
|
||||
lfs_size_t chunk = lfs_min32(
|
||||
TEST_PRNG(&prng) % CHUNK,
|
||||
(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);
|
||||
}
|
||||
if (chunk != 0) {
|
||||
size = lfs_max32(size, off+chunk);
|
||||
}
|
||||
size = lfs_max32(size, off+chunk);
|
||||
|
||||
// update file
|
||||
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
@@ -1828,7 +1826,7 @@ defines.SIZE = [
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
# chunk is more an upper limit here
|
||||
defines.CHUNK = [32, 8]
|
||||
defines.CHUNK = [64, 16]
|
||||
defines.SEED = 'range(10)'
|
||||
fuzz = 'SEED'
|
||||
if = [
|
||||
@@ -1861,7 +1859,7 @@ code = '''
|
||||
lfs_soff_t off = TEST_PRNG(&prng) % SIZE;
|
||||
// and a random size, up to the chunk size
|
||||
lfs_size_t chunk = lfs_min32(
|
||||
TEST_PRNG(&prng) % CHUNK,
|
||||
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
||||
SIZE - off);
|
||||
|
||||
// test different seek methods
|
||||
@@ -1907,7 +1905,7 @@ defines.SIZE = [
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
# chunk is more an upper limit here
|
||||
defines.CHUNK = [32, 8]
|
||||
defines.CHUNK = [64, 16]
|
||||
# INIT=0 => no init
|
||||
# INIT=1 => fill with data
|
||||
# INIT=2 => truncate to size
|
||||
@@ -1956,7 +1954,7 @@ code = '''
|
||||
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
|
||||
// and a random size, up to the chunk size
|
||||
lfs_size_t chunk = lfs_min32(
|
||||
TEST_PRNG(&prng) % CHUNK,
|
||||
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
||||
SIZE - off);
|
||||
|
||||
// test different seek methods
|
||||
@@ -1975,9 +1973,7 @@ code = '''
|
||||
for (lfs_size_t j = 0; j < chunk; j++) {
|
||||
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
if (chunk != 0) {
|
||||
size = lfs_max32(size, off+chunk);
|
||||
}
|
||||
size = lfs_max32(size, off+chunk);
|
||||
|
||||
// update the file
|
||||
lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk;
|
||||
@@ -2056,7 +2052,7 @@ defines.SIZE = [
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
# chunk is more an upper limit here
|
||||
defines.CHUNK = [32, 8]
|
||||
defines.CHUNK = [64, 16]
|
||||
# INIT=0 => no init
|
||||
# INIT=1 => fill with data
|
||||
# INIT=2 => truncate to size
|
||||
@@ -2105,7 +2101,7 @@ code = '''
|
||||
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
|
||||
// and a random size, up to the chunk size
|
||||
lfs_size_t chunk = lfs_min32(
|
||||
TEST_PRNG(&prng) % CHUNK,
|
||||
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
||||
SIZE - off);
|
||||
// and if we are reading or writing
|
||||
uint8_t op = TEST_PRNG(&prng) % 2;
|
||||
@@ -2128,9 +2124,7 @@ code = '''
|
||||
for (lfs_size_t j = 0; j < chunk; j++) {
|
||||
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
if (chunk != 0) {
|
||||
size = lfs_max32(size, off+chunk);
|
||||
}
|
||||
size = lfs_max32(size, off+chunk);
|
||||
|
||||
// update the file
|
||||
lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk;
|
||||
@@ -2336,7 +2330,7 @@ defines.SIZE = [
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
# chunk is more an upper limit here
|
||||
defines.CHUNK = [32, 8]
|
||||
defines.CHUNK = [64, 16]
|
||||
# INIT=0 => no init
|
||||
# INIT=1 => fill with data
|
||||
# INIT=2 => truncate to size
|
||||
@@ -2401,7 +2395,7 @@ code = '''
|
||||
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
|
||||
// and a random size, up to the chunk size
|
||||
lfs_size_t chunk = lfs_min32(
|
||||
TEST_PRNG(&prng) % CHUNK,
|
||||
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
||||
SIZE - off);
|
||||
|
||||
// seek
|
||||
@@ -2411,9 +2405,7 @@ code = '''
|
||||
for (lfs_size_t j = 0; j < chunk; j++) {
|
||||
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
if (chunk != 0) {
|
||||
size = lfs_max32(size, off+chunk);
|
||||
}
|
||||
size = lfs_max32(size, off+chunk);
|
||||
|
||||
// update the file
|
||||
lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk;
|
||||
@@ -2437,7 +2429,7 @@ code = '''
|
||||
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
|
||||
// and a random size, up to the chunk size
|
||||
lfs_size_t chunk = lfs_min32(
|
||||
TEST_PRNG(&prng) % CHUNK,
|
||||
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
|
||||
SIZE - off);
|
||||
|
||||
// seek
|
||||
|
||||
@@ -1,3 +1,797 @@
|
||||
# Many tests already test the internal machinery under powerloss, these
|
||||
# tests are more interested in running the filesystem under
|
||||
# difficult/weird powerloss environments
|
||||
#
|
||||
# Try to keep these below O(n^2), which can be tricky
|
||||
after = [
|
||||
'test_mtree',
|
||||
'test_dirs',
|
||||
'test_files',
|
||||
'test_forphans',
|
||||
]
|
||||
|
||||
|
||||
# Create many dirs under powerloss
|
||||
#
|
||||
# We always make progress so this should be O(n) progs, (but maybe
|
||||
# O(n^2) reads)
|
||||
#
|
||||
[cases.test_powerloss_dir_many]
|
||||
defines.POWERLOSS_BEHAVIOR = [
|
||||
'LFS_EMUBD_POWERLOSS_NOOP',
|
||||
'LFS_EMUBD_POWERLOSS_OOO',
|
||||
]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
|
||||
reentrant = true
|
||||
code = '''
|
||||
// format once per test
|
||||
lfs_t lfs;
|
||||
int err = lfsr_mount(&lfs, CFG);
|
||||
if (err) {
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// make this many directories
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "dir%03x", i);
|
||||
err = lfsr_mkdir(&lfs, name);
|
||||
assert(!err || (TEST_PLS && err == LFS_ERR_EXIST));
|
||||
}
|
||||
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// grm should be zero here
|
||||
assert(lfs.grm_p[0] == 0);
|
||||
|
||||
// check that our mkdir worked
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "dir%03x", i);
|
||||
struct lfs_info info;
|
||||
lfsr_stat(&lfs, name, &info) => 0;
|
||||
assert(strcmp(info.name, name) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
}
|
||||
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
||||
struct lfs_info info;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "dir%03x", i);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, name) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
}
|
||||
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "dir%03x", i);
|
||||
lfsr_dir_open(&lfs, &dir, name) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# Create many files under powerloss
|
||||
#
|
||||
# We always make progress so this should be O(n) progs, (but maybe
|
||||
# O(n^2) reads)
|
||||
#
|
||||
[cases.test_powerloss_file_many]
|
||||
defines.POWERLOSS_BEHAVIOR = [
|
||||
'LFS_EMUBD_POWERLOSS_NOOP',
|
||||
'LFS_EMUBD_POWERLOSS_OOO',
|
||||
]
|
||||
# inlining has a tendency to hide sync issues, so try without
|
||||
defines.INLINE_SIZE = ['BLOCK_SIZE/4', '0']
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
||||
reentrant = true
|
||||
code = '''
|
||||
// format once per test
|
||||
lfs_t lfs;
|
||||
int err = lfsr_mount(&lfs, CFG);
|
||||
if (err) {
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// create this many files
|
||||
uint32_t prng = 42;
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "amethyst%03x", i);
|
||||
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
|
||||
lfsr_file_t file;
|
||||
err = lfsr_file_open(&lfs, &file, name,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL);
|
||||
assert(!err || (TEST_PLS && err == LFS_ERR_EXIST));
|
||||
if (!err) {
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// check that our writes worked
|
||||
prng = 42;
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
// check with stat
|
||||
char name[256];
|
||||
sprintf(name, "amethyst%03x", i);
|
||||
struct lfs_info info;
|
||||
lfsr_stat(&lfs, name, &info) => 0;
|
||||
assert(strcmp(info.name, name) == 0);
|
||||
assert(info.type == LFS_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
|
||||
// try reading the file, note we reset prng above
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
|
||||
lfsr_file_t file;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# A general purpose powerloss fuzz test
|
||||
#
|
||||
# Under powerloss, we can't really keep track of a sim reliably/
|
||||
# efficiently, instead just do random operations, store a counter in a
|
||||
# special file so we know how much progress has been made, and hope for
|
||||
# the best. Most likely an internal assert will trigger if anything goes
|
||||
# wrong.
|
||||
#
|
||||
[cases.test_powerloss_file_pl_fuzz]
|
||||
defines.POWERLOSS_BEHAVIOR = [
|
||||
'LFS_EMUBD_POWERLOSS_NOOP',
|
||||
'LFS_EMUBD_POWERLOSS_OOO',
|
||||
]
|
||||
# inlining has a tendency to hide sync issues, so try without
|
||||
defines.INLINE_SIZE = ['BLOCK_SIZE/4', '0']
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.OPS = 1024
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
defines.SEED = 'range(10)'
|
||||
fuzz = 'SEED'
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
||||
reentrant = true
|
||||
code = '''
|
||||
// format once per test
|
||||
lfs_t lfs;
|
||||
int err = lfsr_mount(&lfs, CFG);
|
||||
if (err) {
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// keep some test state on disk to survive powerloss
|
||||
typedef struct fuzz_state {
|
||||
lfs_size_t i;
|
||||
uint32_t prng;
|
||||
} fuzz_state_t;
|
||||
fuzz_state_t state = {.i = 0, .prng = SEED};
|
||||
|
||||
lfsr_file_t state_file;
|
||||
lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDWR | LFS_O_CREAT) => 0;
|
||||
lfs_ssize_t d = lfsr_file_read(&lfs, &state_file, &state, sizeof(state));
|
||||
assert(d == 0 || d == sizeof(state));
|
||||
|
||||
// keep test files in a separate directory
|
||||
err = lfsr_mkdir(&lfs, "test");
|
||||
assert(!err || err == LFS_ERR_EXIST);
|
||||
|
||||
uint32_t prng = state.prng;
|
||||
for (lfs_size_t i = state.i; i < OPS; i++) {
|
||||
// choose which operation to do
|
||||
uint8_t op = TEST_PRNG(&prng) % 3;
|
||||
|
||||
// how many files do we have?
|
||||
lfs_size_t count = 0;
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
struct lfs_info info;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
while (true) {
|
||||
int err = lfsr_dir_read(&lfs, &dir, &info);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0);
|
||||
assert(info.type == LFS_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
count++;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// creating a new file?
|
||||
if (op == 0 || count == 0) {
|
||||
// choose a pseudo-random number
|
||||
lfs_size_t x = TEST_PRNG(&prng) % N;
|
||||
uint32_t wprng = TEST_PRNG(&prng);
|
||||
|
||||
// create a file here
|
||||
char name[256];
|
||||
sprintf(name, "test/amethyst%03x", x);
|
||||
uint8_t wbuf[SIZE];
|
||||
uint8_t ck = 0;
|
||||
for (lfs_size_t j = 0; j < SIZE-1; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
||||
ck = (ck + (wbuf[j] - 'a')) % 26;
|
||||
}
|
||||
// make the sum equal to 'a' mod 26
|
||||
if (SIZE > 0) {
|
||||
wbuf[SIZE-1] = 'a' + ((26 - ck) % 26);
|
||||
}
|
||||
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, name,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// deleting a file?
|
||||
} else if (op == 1) {
|
||||
// choose a random file to delete
|
||||
lfs_size_t j = TEST_PRNG(&prng) % count;
|
||||
// find the file
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// delete this file
|
||||
char name[256];
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
sprintf(name, "test/%s", info.name);
|
||||
lfsr_remove(&lfs, name) => 0;
|
||||
|
||||
// renaming a file?
|
||||
} else {
|
||||
// choose a random file to rename, and a random number to
|
||||
// rename to
|
||||
lfs_size_t j = TEST_PRNG(&prng) % count;
|
||||
lfs_size_t y = TEST_PRNG(&prng) % N;
|
||||
// find the file
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// rename this file
|
||||
char old_name[256];
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
sprintf(old_name, "test/%s", info.name);
|
||||
char new_name[256];
|
||||
sprintf(new_name, "test/amethyst%03x", y);
|
||||
lfsr_rename(&lfs, old_name, new_name) => 0;
|
||||
}
|
||||
|
||||
// update our state file
|
||||
state.i = i;
|
||||
state.prng = prng;
|
||||
lfsr_file_rewind(&lfs, &state_file) => 0;
|
||||
lfsr_file_write(&lfs, &state_file, &state, sizeof(state))
|
||||
=> sizeof(state);
|
||||
lfsr_file_sync(&lfs, &state_file) => 0;
|
||||
}
|
||||
|
||||
// go ahead and close our state file in case we remount
|
||||
lfsr_file_close(&lfs, &state_file) => 0;
|
||||
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// check that things look more-or-less ok
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
struct lfs_info info;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
while (true) {
|
||||
int err = lfsr_dir_read(&lfs, &dir, &info);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0);
|
||||
assert(info.type == LFS_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
|
||||
// at least try to read the files
|
||||
char name[256];
|
||||
sprintf(name, "test/%s", info.name);
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
||||
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
// all data should be lowercase ascii
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
assert(rbuf[j] >= 'a' && rbuf[j] <= 'z');
|
||||
}
|
||||
// sum should be equal to 'a' mod 26
|
||||
uint8_t ck = 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
ck = (ck + (rbuf[j] - 'a')) % 26;
|
||||
}
|
||||
assert(ck == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# A general purpose powerloss fuzz test, with directories!
|
||||
#
|
||||
# Under powerloss, we can't really keep track of a sim reliably/
|
||||
# efficiently, instead just do random operations, store a counter in a
|
||||
# special file so we know how much progress has been made, and hope for
|
||||
# the best. Most likely an internal assert will trigger if anything goes
|
||||
# wrong.
|
||||
#
|
||||
[cases.test_powerloss_filedir_pl_fuzz]
|
||||
defines.POWERLOSS_BEHAVIOR = [
|
||||
'LFS_EMUBD_POWERLOSS_NOOP',
|
||||
'LFS_EMUBD_POWERLOSS_OOO',
|
||||
]
|
||||
# inlining has a tendency to hide sync issues, so try without
|
||||
defines.INLINE_SIZE = ['BLOCK_SIZE/4', '0']
|
||||
# note dirs x files grows O(n^2)
|
||||
defines.N = [1, 2, 4, 8]
|
||||
defines.M = 'N'
|
||||
defines.OPS = 1024
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
defines.SEED = 'range(10)'
|
||||
fuzz = 'SEED'
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
||||
reentrant = true
|
||||
code = '''
|
||||
// format once per test
|
||||
lfs_t lfs;
|
||||
int err = lfsr_mount(&lfs, CFG);
|
||||
if (err) {
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// keep some test state on disk to survive powerloss
|
||||
typedef struct fuzz_state {
|
||||
lfs_size_t i;
|
||||
uint32_t prng;
|
||||
} fuzz_state_t;
|
||||
fuzz_state_t state = {.i = 0, .prng = SEED};
|
||||
|
||||
lfsr_file_t state_file;
|
||||
lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDWR | LFS_O_CREAT) => 0;
|
||||
lfs_ssize_t d = lfsr_file_read(&lfs, &state_file, &state, sizeof(state));
|
||||
assert(d == 0 || d == sizeof(state));
|
||||
|
||||
// keep test files in a separate directory
|
||||
err = lfsr_mkdir(&lfs, "test");
|
||||
assert(!err || err == LFS_ERR_EXIST);
|
||||
|
||||
uint32_t prng = state.prng;
|
||||
for (lfs_size_t i = state.i; i < OPS; i++) {
|
||||
// choose which operation to do
|
||||
uint8_t op = TEST_PRNG(&prng) % 6;
|
||||
|
||||
// how many dirs do we have?
|
||||
lfs_size_t dir_count = 0;
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
struct lfs_info info;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
while (true) {
|
||||
int err = lfsr_dir_read(&lfs, &dir, &info);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info.name) == strlen("quartz..."));
|
||||
assert(memcmp(info.name, "quartz", strlen("quartz")) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
dir_count++;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// dir op?
|
||||
if (op < 3 || dir_count == 0) {
|
||||
// creating a new dir?
|
||||
if (op == 0 || dir_count == 0) {
|
||||
// choose a pseudo-random number
|
||||
lfs_size_t x = TEST_PRNG(&prng) % N;
|
||||
|
||||
// create a dir here
|
||||
char name[256];
|
||||
sprintf(name, "test/quartz%03x", x);
|
||||
int err = lfsr_mkdir(&lfs, name);
|
||||
assert(!err || err == LFS_ERR_EXIST);
|
||||
|
||||
// deleting a dir?
|
||||
} else if (op == 1) {
|
||||
// choose a random dir to delete
|
||||
lfs_size_t j = TEST_PRNG(&prng) % dir_count;
|
||||
// find the dir
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// try to delete this dir, ignore non-empty dirs!
|
||||
char name[256];
|
||||
assert(strlen(info.name) == strlen("quartz..."));
|
||||
sprintf(name, "test/%s", info.name);
|
||||
int err = lfsr_remove(&lfs, name);
|
||||
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||
|
||||
// renaming a dir?
|
||||
} else {
|
||||
// choose a random dir to rename, and a random number to
|
||||
// rename to
|
||||
lfs_size_t j = TEST_PRNG(&prng) % dir_count;
|
||||
lfs_size_t y = TEST_PRNG(&prng) % N;
|
||||
// find the dir
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// rename this dir, ignore conflicts!
|
||||
char old_name[256];
|
||||
assert(strlen(info.name) == strlen("quartz..."));
|
||||
sprintf(old_name, "test/%s", info.name);
|
||||
char new_name[256];
|
||||
sprintf(new_name, "test/quartz%03x", y);
|
||||
int err = lfsr_rename(&lfs, old_name, new_name);
|
||||
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
// file op?
|
||||
} else {
|
||||
// choose a pseudo-random dir
|
||||
lfs_size_t dir_i = TEST_PRNG(&prng) % dir_count;
|
||||
// find the dir
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= dir_i; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
char dir_path[256];
|
||||
sprintf(dir_path, "test/%s", info.name);
|
||||
|
||||
// how many files do we have?
|
||||
lfs_size_t count = 0;
|
||||
lfsr_dir_open(&lfs, &dir, dir_path) => 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);
|
||||
while (true) {
|
||||
int err = lfsr_dir_read(&lfs, &dir, &info);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
assert(memcmp(
|
||||
info.name,
|
||||
"amethyst", strlen("amethyst")) == 0);
|
||||
assert(info.type == LFS_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
count++;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// creating a new file?
|
||||
if (op == 3 || count == 0) {
|
||||
// choose a pseudo-random number
|
||||
lfs_size_t x = TEST_PRNG(&prng) % M;
|
||||
uint32_t wprng = TEST_PRNG(&prng);
|
||||
|
||||
// create a file here
|
||||
char name[256];
|
||||
sprintf(name, "%s/amethyst%03x", dir_path, x);
|
||||
uint8_t wbuf[SIZE];
|
||||
uint8_t ck = 0;
|
||||
for (lfs_size_t j = 0; j < SIZE-1; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
||||
ck = (ck + (wbuf[j] - 'a')) % 26;
|
||||
}
|
||||
// make the sum equal to 'a' mod 26
|
||||
if (SIZE > 0) {
|
||||
wbuf[SIZE-1] = 'a' + ((26 - ck) % 26);
|
||||
}
|
||||
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, name,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// deleting a file?
|
||||
} else if (op == 4) {
|
||||
// choose a random file to delete
|
||||
lfs_size_t j = TEST_PRNG(&prng) % count;
|
||||
// find the file
|
||||
lfsr_dir_open(&lfs, &dir, dir_path) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// delete this file
|
||||
char name[256];
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
sprintf(name, "%s/%s", dir_path, info.name);
|
||||
lfsr_remove(&lfs, name) => 0;
|
||||
|
||||
// renaming a file?
|
||||
} else {
|
||||
// choose a random file to rename
|
||||
lfs_size_t j = TEST_PRNG(&prng) % count;
|
||||
// find the file
|
||||
lfsr_dir_open(&lfs, &dir, dir_path) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// choose a random dir to rename to
|
||||
lfs_size_t dir_j = TEST_PRNG(&prng) % dir_count;
|
||||
// find the dir
|
||||
struct lfs_info info_;
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info_) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info_) => 0;
|
||||
for (lfs_size_t k = 0; k <= dir_j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info_) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// choose a random file to rename to
|
||||
lfs_size_t y = TEST_PRNG(&prng) % M;
|
||||
|
||||
// rename this file
|
||||
char old_name[256];
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
sprintf(old_name, "%s/%s", dir_path, info.name);
|
||||
char new_name[256];
|
||||
sprintf(new_name, "test/%s/amethyst%03x", info_.name, y);
|
||||
lfsr_rename(&lfs, old_name, new_name) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
// update our state file
|
||||
state.i = i;
|
||||
state.prng = prng;
|
||||
lfsr_file_rewind(&lfs, &state_file) => 0;
|
||||
lfsr_file_write(&lfs, &state_file, &state, sizeof(state))
|
||||
=> sizeof(state);
|
||||
lfsr_file_sync(&lfs, &state_file) => 0;
|
||||
}
|
||||
|
||||
// go ahead and close our state file in case we remount
|
||||
lfsr_file_close(&lfs, &state_file) => 0;
|
||||
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// check that things look more-or-less ok
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
struct lfs_info info;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
while (true) {
|
||||
int err = lfsr_dir_read(&lfs, &dir, &info);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info.name) == strlen("quartz..."));
|
||||
assert(memcmp(info.name, "quartz", strlen("quartz")) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
|
||||
// check that our dirs look more-or-less ok
|
||||
char name[256];
|
||||
sprintf(name, "test/%s", info.name);
|
||||
lfsr_dir_t dir_;
|
||||
lfsr_dir_open(&lfs, &dir_, name) => 0;
|
||||
struct lfs_info info_;
|
||||
lfsr_dir_read(&lfs, &dir_, &info_) => 0;
|
||||
assert(strcmp(info_.name, ".") == 0);
|
||||
assert(info_.type == LFS_TYPE_DIR);
|
||||
assert(info_.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir_, &info_) => 0;
|
||||
assert(strcmp(info_.name, "..") == 0);
|
||||
assert(info_.type == LFS_TYPE_DIR);
|
||||
assert(info_.size == 0);
|
||||
|
||||
while (true) {
|
||||
err = lfsr_dir_read(&lfs, &dir_, &info_);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info_.name) == strlen("amethyst..."));
|
||||
assert(memcmp(
|
||||
info_.name,
|
||||
"amethyst", strlen("amethyst")) == 0);
|
||||
assert(info_.type == LFS_TYPE_REG);
|
||||
assert(info_.size == SIZE);
|
||||
|
||||
// at least try to read the files
|
||||
sprintf(name, "test/%s/%s", info.name, info_.name);
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
||||
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
// all data should be lowercase ascii
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
assert(rbuf[j] >= 'a' && rbuf[j] <= 'z');
|
||||
}
|
||||
// sum should be equal to 'a' mod 26
|
||||
uint8_t ck = 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
ck = (ck + (rbuf[j] - 'a')) % 26;
|
||||
}
|
||||
assert(ck == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir_) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## There are already a number of tests that test general operations under
|
||||
## power-loss (see the reentrant attribute). These tests are for explicitly
|
||||
## testing specific corner cases.
|
||||
|
||||
+525
-1
@@ -4,6 +4,7 @@ after = [
|
||||
'test_dirs',
|
||||
'test_files',
|
||||
'test_forphans',
|
||||
'test_powerloss',
|
||||
]
|
||||
|
||||
# Note that most of the delicate relocation operations are already tested
|
||||
@@ -11,6 +12,85 @@ after = [
|
||||
# relatively aggressive wear-leveling.
|
||||
|
||||
# dirs + relocations may create problems for gstate
|
||||
[cases.test_relocations_dir_many]
|
||||
defines.BLOCK_RECYCLES = [4, 1, 0]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
|
||||
// make this many directories
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "dir%03x", i);
|
||||
int err = lfsr_mkdir(&lfs, name);
|
||||
assert(!err || (TEST_PLS && err == LFS_ERR_EXIST));
|
||||
}
|
||||
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// grm should be zero here
|
||||
assert(lfs.grm_p[0] == 0);
|
||||
|
||||
// check that our mkdir worked
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "dir%03x", i);
|
||||
struct lfs_info info;
|
||||
lfsr_stat(&lfs, name, &info) => 0;
|
||||
assert(strcmp(info.name, name) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
}
|
||||
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
||||
struct lfs_info info;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "dir%03x", i);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, name) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
}
|
||||
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "dir%03x", i);
|
||||
lfsr_dir_open(&lfs, &dir, name) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
[cases.test_relocations_dir_fuzz]
|
||||
defines.BLOCK_RECYCLES = [4, 1, 0]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
|
||||
@@ -162,6 +242,79 @@ code = '''
|
||||
'''
|
||||
|
||||
# files + relocations may create problems for shrubs
|
||||
[cases.test_relocations_file_many]
|
||||
defines.BLOCK_RECYCLES = [4, 1, 0]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
|
||||
// create this many files
|
||||
uint32_t prng = 42;
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "amethyst%03x", i);
|
||||
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, name,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// check that our writes worked
|
||||
prng = 42;
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
// check with stat
|
||||
char name[256];
|
||||
sprintf(name, "amethyst%03x", i);
|
||||
struct lfs_info info;
|
||||
lfsr_stat(&lfs, name, &info) => 0;
|
||||
assert(strcmp(info.name, name) == 0);
|
||||
assert(info.type == LFS_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
|
||||
// try reading the file, note we reset prng above
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
|
||||
lfsr_file_t file;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
[cases.test_relocations_file_fuzz]
|
||||
defines.BLOCK_RECYCLES = [4, 1, 0]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
@@ -1133,6 +1286,9 @@ code = '''
|
||||
|
||||
|
||||
# and don't forget potential powerloss problems
|
||||
|
||||
# A general purpose powerloss fuzz test
|
||||
#
|
||||
#
|
||||
# Under powerloss, we can't really keep track of a sim reliably/
|
||||
# efficiently, instead just do random operations, store a counter in a
|
||||
@@ -1140,7 +1296,7 @@ code = '''
|
||||
# the best. Most likely an internal assert will trigger if anything goes
|
||||
# wrong.
|
||||
#
|
||||
[cases.test_relocations_pl_fuzz]
|
||||
[cases.test_relocations_file_pl_fuzz]
|
||||
defines.BLOCK_RECYCLES = [4, 1, 0]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.OPS = 256
|
||||
@@ -1333,6 +1489,10 @@ code = '''
|
||||
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
// all data should be lowercase ascii
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
assert(rbuf[j] >= 'a' && rbuf[j] <= 'z');
|
||||
}
|
||||
// sum should be equal to 'a' mod 26
|
||||
uint8_t ck = 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
@@ -1347,6 +1507,370 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# A general purpose powerloss fuzz test, with directories!
|
||||
#
|
||||
# Under powerloss, we can't really keep track of a sim reliably/
|
||||
# efficiently, instead just do random operations, store a counter in a
|
||||
# special file so we know how much progress has been made, and hope for
|
||||
# the best. Most likely an internal assert will trigger if anything goes
|
||||
# wrong.
|
||||
#
|
||||
[cases.test_relocations_filedir_pl_fuzz]
|
||||
defines.BLOCK_RECYCLES = [4, 1, 0]
|
||||
# note dirs x files grows O(n^2)
|
||||
defines.N = [1, 2, 4, 8]
|
||||
defines.M = 'N'
|
||||
defines.OPS = 256
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'4*BLOCK_SIZE',
|
||||
]
|
||||
defines.SEED = 'range(10)'
|
||||
fuzz = 'SEED'
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
||||
reentrant = true
|
||||
code = '''
|
||||
// format once per test
|
||||
lfs_t lfs;
|
||||
int err = lfsr_mount(&lfs, CFG);
|
||||
if (err) {
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// keep some test state on disk to survive powerloss
|
||||
typedef struct fuzz_state {
|
||||
lfs_size_t i;
|
||||
uint32_t prng;
|
||||
} fuzz_state_t;
|
||||
fuzz_state_t state = {.i = 0, .prng = SEED};
|
||||
|
||||
lfsr_file_t state_file;
|
||||
lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDWR | LFS_O_CREAT) => 0;
|
||||
lfs_ssize_t d = lfsr_file_read(&lfs, &state_file, &state, sizeof(state));
|
||||
assert(d == 0 || d == sizeof(state));
|
||||
|
||||
// keep test files in a separate directory
|
||||
err = lfsr_mkdir(&lfs, "test");
|
||||
assert(!err || err == LFS_ERR_EXIST);
|
||||
|
||||
uint32_t prng = state.prng;
|
||||
for (lfs_size_t i = state.i; i < OPS; i++) {
|
||||
// choose which operation to do
|
||||
uint8_t op = TEST_PRNG(&prng) % 6;
|
||||
|
||||
// how many dirs do we have?
|
||||
lfs_size_t dir_count = 0;
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
struct lfs_info info;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
while (true) {
|
||||
int err = lfsr_dir_read(&lfs, &dir, &info);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info.name) == strlen("quartz..."));
|
||||
assert(memcmp(info.name, "quartz", strlen("quartz")) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
dir_count++;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// dir op?
|
||||
if (op < 3 || dir_count == 0) {
|
||||
// creating a new dir?
|
||||
if (op == 0 || dir_count == 0) {
|
||||
// choose a pseudo-random number
|
||||
lfs_size_t x = TEST_PRNG(&prng) % N;
|
||||
|
||||
// create a dir here
|
||||
char name[256];
|
||||
sprintf(name, "test/quartz%03x", x);
|
||||
int err = lfsr_mkdir(&lfs, name);
|
||||
assert(!err || err == LFS_ERR_EXIST);
|
||||
|
||||
// deleting a dir?
|
||||
} else if (op == 1) {
|
||||
// choose a random dir to delete
|
||||
lfs_size_t j = TEST_PRNG(&prng) % dir_count;
|
||||
// find the dir
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// try to delete this dir, ignore non-empty dirs!
|
||||
char name[256];
|
||||
assert(strlen(info.name) == strlen("quartz..."));
|
||||
sprintf(name, "test/%s", info.name);
|
||||
int err = lfsr_remove(&lfs, name);
|
||||
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||
|
||||
// renaming a dir?
|
||||
} else {
|
||||
// choose a random dir to rename, and a random number to
|
||||
// rename to
|
||||
lfs_size_t j = TEST_PRNG(&prng) % dir_count;
|
||||
lfs_size_t y = TEST_PRNG(&prng) % N;
|
||||
// find the dir
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// rename this dir, ignore conflicts!
|
||||
char old_name[256];
|
||||
assert(strlen(info.name) == strlen("quartz..."));
|
||||
sprintf(old_name, "test/%s", info.name);
|
||||
char new_name[256];
|
||||
sprintf(new_name, "test/quartz%03x", y);
|
||||
int err = lfsr_rename(&lfs, old_name, new_name);
|
||||
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
// file op?
|
||||
} else {
|
||||
// choose a pseudo-random dir
|
||||
lfs_size_t dir_i = TEST_PRNG(&prng) % dir_count;
|
||||
// find the dir
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= dir_i; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
char dir_path[256];
|
||||
sprintf(dir_path, "test/%s", info.name);
|
||||
|
||||
// how many files do we have?
|
||||
lfs_size_t count = 0;
|
||||
lfsr_dir_open(&lfs, &dir, dir_path) => 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);
|
||||
while (true) {
|
||||
int err = lfsr_dir_read(&lfs, &dir, &info);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
assert(memcmp(
|
||||
info.name,
|
||||
"amethyst", strlen("amethyst")) == 0);
|
||||
assert(info.type == LFS_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
count++;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// creating a new file?
|
||||
if (op == 3 || count == 0) {
|
||||
// choose a pseudo-random number
|
||||
lfs_size_t x = TEST_PRNG(&prng) % M;
|
||||
uint32_t wprng = TEST_PRNG(&prng);
|
||||
|
||||
// create a file here
|
||||
char name[256];
|
||||
sprintf(name, "%s/amethyst%03x", dir_path, x);
|
||||
uint8_t wbuf[SIZE];
|
||||
uint8_t ck = 0;
|
||||
for (lfs_size_t j = 0; j < SIZE-1; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
||||
ck = (ck + (wbuf[j] - 'a')) % 26;
|
||||
}
|
||||
// make the sum equal to 'a' mod 26
|
||||
if (SIZE > 0) {
|
||||
wbuf[SIZE-1] = 'a' + ((26 - ck) % 26);
|
||||
}
|
||||
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, name,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// deleting a file?
|
||||
} else if (op == 4) {
|
||||
// choose a random file to delete
|
||||
lfs_size_t j = TEST_PRNG(&prng) % count;
|
||||
// find the file
|
||||
lfsr_dir_open(&lfs, &dir, dir_path) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// delete this file
|
||||
char name[256];
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
sprintf(name, "%s/%s", dir_path, info.name);
|
||||
lfsr_remove(&lfs, name) => 0;
|
||||
|
||||
// renaming a file?
|
||||
} else {
|
||||
// choose a random file to rename
|
||||
lfs_size_t j = TEST_PRNG(&prng) % count;
|
||||
// find the file
|
||||
lfsr_dir_open(&lfs, &dir, dir_path) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
for (lfs_size_t k = 0; k <= j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// choose a random dir to rename to
|
||||
lfs_size_t dir_j = TEST_PRNG(&prng) % dir_count;
|
||||
// find the dir
|
||||
struct lfs_info info_;
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info_) => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info_) => 0;
|
||||
for (lfs_size_t k = 0; k <= dir_j; k++) {
|
||||
lfsr_dir_read(&lfs, &dir, &info_) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// choose a random file to rename to
|
||||
lfs_size_t y = TEST_PRNG(&prng) % M;
|
||||
|
||||
// rename this file
|
||||
char old_name[256];
|
||||
assert(strlen(info.name) == strlen("amethyst..."));
|
||||
sprintf(old_name, "%s/%s", dir_path, info.name);
|
||||
char new_name[256];
|
||||
sprintf(new_name, "test/%s/amethyst%03x", info_.name, y);
|
||||
lfsr_rename(&lfs, old_name, new_name) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
// update our state file
|
||||
state.i = i;
|
||||
state.prng = prng;
|
||||
lfsr_file_rewind(&lfs, &state_file) => 0;
|
||||
lfsr_file_write(&lfs, &state_file, &state, sizeof(state))
|
||||
=> sizeof(state);
|
||||
lfsr_file_sync(&lfs, &state_file) => 0;
|
||||
}
|
||||
|
||||
// go ahead and close our state file in case we remount
|
||||
lfsr_file_close(&lfs, &state_file) => 0;
|
||||
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// check that things look more-or-less ok
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "test") => 0;
|
||||
struct lfs_info info;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
while (true) {
|
||||
int err = lfsr_dir_read(&lfs, &dir, &info);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info.name) == strlen("quartz..."));
|
||||
assert(memcmp(info.name, "quartz", strlen("quartz")) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
|
||||
// check that our dirs look more-or-less ok
|
||||
char name[256];
|
||||
sprintf(name, "test/%s", info.name);
|
||||
lfsr_dir_t dir_;
|
||||
lfsr_dir_open(&lfs, &dir_, name) => 0;
|
||||
struct lfs_info info_;
|
||||
lfsr_dir_read(&lfs, &dir_, &info_) => 0;
|
||||
assert(strcmp(info_.name, ".") == 0);
|
||||
assert(info_.type == LFS_TYPE_DIR);
|
||||
assert(info_.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir_, &info_) => 0;
|
||||
assert(strcmp(info_.name, "..") == 0);
|
||||
assert(info_.type == LFS_TYPE_DIR);
|
||||
assert(info_.size == 0);
|
||||
|
||||
while (true) {
|
||||
err = lfsr_dir_read(&lfs, &dir_, &info_);
|
||||
assert(!err || err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
assert(strlen(info_.name) == strlen("amethyst..."));
|
||||
assert(memcmp(
|
||||
info_.name,
|
||||
"amethyst", strlen("amethyst")) == 0);
|
||||
assert(info_.type == LFS_TYPE_REG);
|
||||
assert(info_.size == SIZE);
|
||||
|
||||
// at least try to read the files
|
||||
sprintf(name, "test/%s/%s", info.name, info_.name);
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
||||
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
// all data should be lowercase ascii
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
assert(rbuf[j] >= 'a' && rbuf[j] <= 'z');
|
||||
}
|
||||
// sum should be equal to 'a' mod 26
|
||||
uint8_t ck = 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
ck = (ck + (rbuf[j] - 'a')) % 26;
|
||||
}
|
||||
assert(ck == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir_) => 0;
|
||||
}
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
|
||||
## specific corner cases worth explicitly testing for
|
||||
|
||||
Reference in New Issue
Block a user