Files
littlefs/tests/test_powerloss.toml
T
Christopher Haster 2402e108db Reduced test_powerloss OPS from 1024 -> 256
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.
2024-06-06 17:42:45 -05:00

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;
#'''