2402e108db
Now that we are testing more powerloss behaviors, test_powerloss is the longest running test suite by a decent margin: Before: $ ./scripts/summary.py test.csv -bsuite -ftime -Stime ... snip ... test_rbyd 578.6 test_fwrite 984.5 test_badblocks 1341.5 test_exhaustion 1648.3 test_powerloss 2192.3 <-- TOTAL 7378.6 $ ./scripts/summary.py test.csv -bcase -ftime -Stime ... snip ... test_fwrite_fuzz_aligned 247.2 test_exhaustion_file_fuzz 287.7 test_exhaustion_dir_fuzz 307.2 test_exhaustion_orphanzombie_fuzz 389.1 test_exhaustion_orphanzombiedir_fuzz 531.5 test_powerloss_file_pl_fuzz 787.7 <-- test_badblocks_single_dir_many 840.2 test_powerloss_filedir_pl_fuzz 1366.7 <-- TOTAL 7378.6 But testing more things is better than testing the same thing more. Worst case you can always manually override OPS, -DOPS=1024, if you have CI cycles to spare. Though note with our linear powerloss heuristic, the tail end of long running tests also recieves fewer powerlosses, which reduces the usefulness of running these tests longer. These *_pl_fuzz tests also now match the default number of OPS in test_relocations.
985 lines
33 KiB
TOML
985 lines
33 KiB
TOML
# 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_SOMEBITS',
|
|
'LFS_EMUBD_POWERLOSS_MOSTBITS',
|
|
'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_SOMEBITS',
|
|
'LFS_EMUBD_POWERLOSS_MOSTBITS',
|
|
'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_SOMEBITS',
|
|
'LFS_EMUBD_POWERLOSS_MOSTBITS',
|
|
'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 = 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) % 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_SOMEBITS',
|
|
'LFS_EMUBD_POWERLOSS_MOSTBITS',
|
|
'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 = 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;
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 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.
|
|
#
|
|
## only a revision count
|
|
#[cases.test_powerloss_only_rev]
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "notebook") => 0;
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "notebook/paper",
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# char buffer[256];
|
|
# strcpy(buffer, "hello");
|
|
# lfs_size_t size = strlen("hello");
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# char rbuffer[256];
|
|
# lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# // get pair/rev count
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_dir_t dir;
|
|
# lfs_dir_open(&lfs, &dir, "notebook") => 0;
|
|
# lfs_block_t pair[2] = {dir.m.pair[0], dir.m.pair[1]};
|
|
# uint32_t rev = dir.m.rev;
|
|
# lfs_dir_close(&lfs, &dir) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# // write just the revision count
|
|
# uint8_t bbuffer[BLOCK_SIZE];
|
|
# cfg->read(cfg, pair[1], 0, bbuffer, BLOCK_SIZE) => 0;
|
|
#
|
|
# memcpy(bbuffer, &(uint32_t){lfs_tole32(rev+1)}, sizeof(uint32_t));
|
|
#
|
|
# cfg->erase(cfg, pair[1]) => 0;
|
|
# cfg->prog(cfg, pair[1], 0, bbuffer, BLOCK_SIZE) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // can read?
|
|
# lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // can write?
|
|
# lfs_file_open(&lfs, &file, "notebook/paper",
|
|
# LFS_O_WRONLY | LFS_O_APPEND) => 0;
|
|
# strcpy(buffer, "goodbye");
|
|
# size = strlen("goodbye");
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
|
# strcpy(buffer, "hello");
|
|
# size = strlen("hello");
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# strcpy(buffer, "goodbye");
|
|
# size = strlen("goodbye");
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## partial prog, may not be byte in order!
|
|
#[cases.test_powerloss_partial_prog]
|
|
#if = "PROG_SIZE < BLOCK_SIZE"
|
|
#defines.BYTE_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"]
|
|
#defines.BYTE_VALUE = [0x33, 0xcc]
|
|
#in = "lfs.c"
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "notebook") => 0;
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "notebook/paper",
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# char buffer[256];
|
|
# strcpy(buffer, "hello");
|
|
# lfs_size_t size = strlen("hello");
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# char rbuffer[256];
|
|
# lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# // imitate a partial prog, value should not matter, if littlefs
|
|
# // doesn't notice the partial prog testbd will assert
|
|
#
|
|
# // get offset to next prog
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_dir_t dir;
|
|
# lfs_dir_open(&lfs, &dir, "notebook") => 0;
|
|
# lfs_block_t block = dir.m.pair[0];
|
|
# lfs_off_t off = dir.m.off;
|
|
# lfs_dir_close(&lfs, &dir) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# // tweak byte
|
|
# uint8_t bbuffer[BLOCK_SIZE];
|
|
# cfg->read(cfg, block, 0, bbuffer, BLOCK_SIZE) => 0;
|
|
#
|
|
# bbuffer[off + BYTE_OFF] = BYTE_VALUE;
|
|
#
|
|
# cfg->erase(cfg, block) => 0;
|
|
# cfg->prog(cfg, block, 0, bbuffer, BLOCK_SIZE) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // can read?
|
|
# lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // can write?
|
|
# lfs_file_open(&lfs, &file, "notebook/paper",
|
|
# LFS_O_WRONLY | LFS_O_APPEND) => 0;
|
|
# strcpy(buffer, "goodbye");
|
|
# size = strlen("goodbye");
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
|
# strcpy(buffer, "hello");
|
|
# size = strlen("hello");
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# strcpy(buffer, "goodbye");
|
|
# size = strlen("goodbye");
|
|
# for (int i = 0; i < 5; i++) {
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|