Files
littlefs/tests/test_badblocks.toml
T
Christopher Haster 458fe16f38 Extended emubd to test metastability, added ckprog/ckread tests
Metastability is a rather nasty error condition where successive reads
to a memory location may return different values, either due to bus
issues or a failed prog. It's a tricky error condition to detect, and
one that ckreads was, in theory, supposed to help with.

To help test metastability (and other single-bit errors), emubd gained
several new features:

- LFS_EMUBD_BADBLOCK_PROGFLIP    - Prog flips a bit
- LFS_EMUBD_BADBLOCK_READFLIP    - Read flips a bit sometimes
- LFS_EMUBD_POWERLOSS_METASTABLE - Reads may flip a bit

These only affect a single bit in a given block, but by randomizing
which bit during every erase (and exhaustive bit testing in test_ck) we
should still see some fairly interesting bit-error patterns over time.

It's a bit difficult to test with more than a single bit error because
you can quickly find checksum/parity collisions when fuzz testing. But
there may be other interesting error patterns to look at in the future?

Also the erase_cycles implementation got a bit of a rework since it was
lopsided previously (progs/reads would always error before erases). And
since I was messing with emubd's internals I added lfs_emubd_markbad/
markgood and a few other convenience functions that seem useful:

- lfs_emubd_seed - Manually set the prng, needed in test_ck actually
- lfs_emubd_markbad - Mark block as bad, same as wear=-1
- lfs_emubd_markgood - Mark block as good, same as wear=0
- lfs_emubd_badbit - Get which big failed
- lfs_emubd_setbadbit - Set which bit will fail
- lfs_emubd_randomizebadbit - Randomize bad bit on erase
- lfs_emubd_markbadbit - Mark bit as bad, same as setbadbit+markbad

---

The intention of this new metastability emulation was to extend test_ck
to test ckreads/ckprogs. This went... interestingly.

The good news, the new emulation and tests worked quite well. They were
able to quite quickly show that ckreads is fundamentally not able to
detect all single-bit errors in our current design.

The problem boils down to the fact that the location of our parity bits
depends on the tag's leb128-encoded size. If a bit flip changes this
size field, we end up with a new parity bit, which 50/50 may or may not
detect the error.

For example, one bit flip:

  40 0c 00 12 80 0d ff ff
  '----.----' ^--------------------.
       '- altble 0xc w0 -18 parity=1

  40 0c 80 12 80 0d ff ff
  '-------.-------' ^----------------------.
          '- altble 0xc w2304 -1664 parity=1

This doesn't make ckreads _completely_ useless, just mostly useless. We
can still use it to check parity bits, but without a systematic proof.

But there's enough problems with ckreads: performance, RAM, code, etc,
that I think it may just be an interesting proof-of-concept and not
something users should actually use. Checking reads in the bd-layer
solves all of these problems...

---

At the very least ckprogs gets better testing, thanks to new tests in
test_ck and the addition of LFS_EMUBD_BADBLOCK_PROGFLIP in
test_badblocks.

The extra testing also found a ckprog/ckread hole in that we don't
ckprog/ckread during lfsr_format! I fixed this by making lfsr_format
always use ckprogs/ckreads if available, but maybe lfsr_format should
take its own set of flags?

Funnily enough this had no impact on code size since it probably just
changed the constant in a constant pool:

          code           stack
  before: 37872           3048
  after:  37872 (+0.0%)   3048 (+0.0%)
2024-08-16 01:03:57 -05:00

5574 lines
185 KiB
TOML

# Bad-block related tests
after = [
'test_dirs',
'test_files',
'test_fwrite',
'test_forphans',
'test_traversal',
'test_gc',
'test_mount',
'test_ck',
'test_compat',
]
## Single badblock tests
#
# first test with every possible single badblock
# B-tree's ridiculous branching factor is great for performance, but it makes
# them a bit of a pain to test, here we test them explicitly
[cases.test_badblocks_single_btree_many]
defines.BADBLOCK = -1
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
# maximize lookahead buffer to avoid alloc scans
defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)'
defines.SEED = 42
fuzz = 'SEED'
in = 'lfs.c'
code = '''
// test all possible bad blocks
for (lfs_size_t i = 0;
i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1);
i++) {
lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK;
// mark our badblock as bad
lfs_emubd_markbad(CFG, badblock) => 0;
printf("--- badblock: 0x%x ---\n", badblock);
// test creating a btree
lfs_t lfs;
lfs_init(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// create free lookahead
memset(lfs.lookahead.buffer, 0, CFG->lookahead_size);
lfs.lookahead.window = 2;
lfs.lookahead.off = 0;
lfs.lookahead.size = lfs_min(8*CFG->lookahead_size,
CFG->block_count-2);
lfs_alloc_ckpoint(&lfs);
// create a btree
lfsr_btree_t btree = LFSR_BTREE_NULL();
// set up a simulation to compare against
char *sim = malloc(N);
lfs_size_t sim_size = 0;
memset(sim, 0, N);
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1);
// add to btree
lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_DATA, +1,
LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0;
// add to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
sim[bid] = 'a'+(i % 26);
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.weight,
btree.blocks[0],
btree.trunk);
assert(btree.weight == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_lookup(&lfs, &btree, i,
&tag_, &weight_, &data_) => 0;
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_DATA);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_lookup(&lfs, &btree, sim_size,
&tag_, &weight_, &data_) => LFS_ERR_NOENT;
// clean up sim
free(sim);
lfs_deinit(&lfs) => 0;
// reset badblock
lfs_emubd_markgood(CFG, badblock) => 0;
}
'''
# with dirs
[cases.test_badblocks_single_dir_many]
defines.BADBLOCK = -1
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
code = '''
// test all possible bad blocks
for (lfs_size_t i = 2;
i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1);
i++) {
lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK;
// mark our badblock as bad
lfs_emubd_markbad(CFG, badblock) => 0;
printf("--- badblock: 0x%x ---\n", badblock);
// test creating directories
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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;
// reset badblock
lfs_emubd_markgood(CFG, badblock) => 0;
}
'''
# fuzz dirs
[cases.test_badblocks_single_dir_fuzz]
defines.BADBLOCK = -1
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
defines.OPS = '2*N'
defines.SEED = 42
fuzz = 'SEED'
code = '''
// test all possible bad blocks
for (lfs_size_t i = 2;
i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1);
i++) {
lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK;
// mark our badblock as bad
lfs_emubd_markbad(CFG, badblock) => 0;
printf("--- badblock: 0x%x ---\n", badblock);
// test fuzz with dirs
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose a pseudo-random op, either mkdir, remove, or rename
uint8_t op = TEST_PRNG(&prng) % 3;
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number, truncate to 3 hexadecimals
lfs_size_t x = TEST_PRNG(&prng) % N;
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// do nothing
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
sim_size += 1;
sim[j] = x;
}
break;
}
}
// create a directory here
char name[256];
sprintf(name, "dir%03x", x);
int err = lfsr_mkdir(&lfs, name);
assert(!err || err == LFS_ERR_EXIST);
} else if (op == 1) {
// choose a pseudo-random entry to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
sim_size -= 1;
// remove this directory
char name[256];
sprintf(name, "dir%03x", x);
lfsr_remove(&lfs, name) => 0;
} else {
// choose a pseudo-random entry to rename, and a pseudo-random
// number to rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// already seen and not a noop?
if (k < sim_size && sim[k] == y && x != y) {
// just delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
sim_size -= 1;
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
sim[k] = y;
}
break;
}
}
// rename this directory
char old_name[256];
sprintf(old_name, "dir%03x", x);
char new_name[256];
sprintf(new_name, "dir%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// grm should be zero here
assert(lfs.grm_p[0] == 0);
// test that our directories match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
char name2[256];
sprintf(name2, "dir%03x", sim[j]);
assert(strcmp(info.name, name2) == 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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
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;
}
// clean up sim/lfs
free(sim);
lfsr_unmount(&lfs) => 0;
// reset badblock
lfs_emubd_markgood(CFG, badblock) => 0;
}
'''
# with files
[cases.test_badblocks_single_file_many]
defines.BADBLOCK = -1
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
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 = '''
// test all possible bad blocks
for (lfs_size_t i = 2;
i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1);
i++) {
lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK;
// mark our badblock as bad
lfs_emubd_markbad(CFG, badblock) => 0;
printf("--- badblock: 0x%x ---\n", badblock);
// test creating files
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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;
// reset badblock
lfs_emubd_markgood(CFG, badblock) => 0;
}
'''
# fuzz files
[cases.test_badblocks_single_file_fuzz]
defines.BADBLOCK = -1
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test all possible bad blocks
for (lfs_size_t i = 2;
i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1);
i++) {
lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK;
// mark our badblock as bad
lfs_emubd_markbad(CFG, badblock) => 0;
printf("--- badblock: 0x%x ---\n", badblock);
// test fuzz with files
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 3;
// creating a new file?
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// associate each file with a prng that generates its contents
uint32_t wprng = TEST_PRNG(&prng);
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// new prng
sim_prngs[j] = wprng;
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
memmove(&sim_prngs[j+1], &sim_prngs[j],
(sim_size-j)*sizeof(uint32_t));
sim_size += 1;
sim[j] = x;
sim_prngs[j] = wprng;
}
break;
}
}
// create a file here
char name[256];
sprintf(name, "amethyst%03x", x);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 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) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// delete this file
char name[256];
sprintf(name, "amethyst%03x", x);
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) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "amethyst%03x", x);
char new_name[256];
sprintf(new_name, "amethyst%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// check that our files match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
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);
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 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;
// check the file contents
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
// reset badblock
lfs_emubd_markgood(CFG, badblock) => 0;
}
'''
# with more complex file writes
[cases.test_badblocks_single_fwrite_fuzz]
defines.BADBLOCK = -1
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.OPS = 20
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 = '''
// test all possible bad blocks
for (lfs_size_t i = 2;
i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1);
i++) {
lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK;
// mark our badblock as bad
lfs_emubd_markbad(CFG, badblock) => 0;
printf("--- badblock: 0x%x ---\n", badblock);
// test with complex file writes
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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 (lfs_size_t i = 0; i < OPS; i++) {
// choose a random location
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
// and a random size, up to the chunk size
lfs_size_t chunk = lfs_min(
TEST_PRNG(&prng) % CHUNK,
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_max(size, off+chunk);
}
// update file
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk;
// sync?
if (SYNC) {
lfsr_file_sync(&lfs, &file) => 0;
}
}
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// check our file with stat
struct lfs_info info;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == size);
// and with dir read
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == size);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// try reading our file
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
// is size correct?
lfsr_file_size(&lfs, &file) => size;
// try reading
uint8_t rbuf[2*SIZE];
memset(rbuf, 0xaa, 2*SIZE);
lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size;
// does our file match our simulation?
assert(memcmp(rbuf, sim, size) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_unmount(&lfs) => 0;
// reset badblock
lfs_emubd_markgood(CFG, badblock) => 0;
}
'''
# with orphans, zombies, etc
[cases.test_badblocks_single_orphanzombie_fuzz]
defines.BADBLOCK = -1
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test all possible bad blocks
for (lfs_size_t i = 2;
i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1);
i++) {
lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK;
// mark our badblock as bad
lfs_emubd_markbad(CFG, badblock) => 0;
printf("--- badblock: 0x%x ---\n", badblock);
// test with orphans, zombies, etc
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
typedef struct sim_file {
lfs_size_t x;
bool orphan;
bool zombie;
uint32_t prng;
lfsr_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
nonsense:;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 5;
// open a new file?
if (op == 0) {
if (sim_file_count >= N) {
goto nonsense;
}
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// already exists?
bool orphan = true;
uint32_t wprng = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim[j] == x) {
orphan = false;
wprng = sim_prngs[j];
break;
}
}
// choose a random seed if we don't exist
if (orphan) {
wprng = TEST_PRNG(&prng);
}
// open in our sim
lfs_size_t j = sim_file_count;
sim_files[j] = malloc(sizeof(sim_file_t));
sim_files[j]->x = x;
sim_files[j]->orphan = orphan;
sim_files[j]->zombie = false;
sim_files[j]->prng = wprng;
sim_file_count++;
// open the actual file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_file_open(&lfs, &sim_files[j]->file, name,
LFS_O_RDWR | LFS_O_CREAT) => 0;
// write some initial data if we don't exist
if (orphan) {
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE)
=> SIZE;
}
// write/rewrite a file?
} else if (op == 1) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng);
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// insert into our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = wprng;
}
break;
}
}
// update related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
sim_files[k]->orphan = false;
sim_files[k]->prng = wprng;
}
}
}
// write to the file
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &sim_files[j]->file)
=> (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT;
// close a file?
} else if (op == 2) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
// this doesn't really test anything, but if we don't close
// files eventually everything will end up zombies
// close the file without affected disk
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
// remove from list
free(sim_files[j]);
sim_files[j] = sim_files[sim_file_count-1];
sim_file_count -= 1;
// remove a file?
} else if (op == 3) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_remove(&lfs, name) => 0;
// rename a file?
} else if (op == 4) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
// update any related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
// move source files
if (sim_files[k]->x == x) {
sim_files[k]->x = y;
// mark target files as zombied
} else if (sim_files[k]->x == y) {
sim_files[k]->zombie = true;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "batman%03x", x);
char new_name[256];
sprintf(new_name, "batman%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
// check that disk matches our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
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);
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 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;
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// check that our file handles match our simulation
for (lfs_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
for (lfs_size_t j = 0; j < sim_file_count; j++) {
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfsr_unmount(&lfs) => 0;
// reset badblock
lfs_emubd_markgood(CFG, badblock) => 0;
}
'''
# with orphans, zombies, dirs, etc
[cases.test_badblocks_single_orphanzombiedir_fuzz]
defines.BADBLOCK = -1
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test all possible bad blocks
for (lfs_size_t i = 2;
i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1);
i++) {
lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK;
// mark our badblock as bad
lfs_emubd_markbad(CFG, badblock) => 0;
printf("--- badblock: 0x%x ---\n", badblock);
// test with orphans, zombies, dirs, etc
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
bool *sim_isdirs = malloc(N*sizeof(bool));
lfs_size_t sim_size = 0;
typedef struct sim_file {
lfs_size_t x;
bool orphan;
bool zombie;
uint32_t prng;
lfsr_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
nonsense:;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 8;
// open a new file?
if (op == 0) {
if (sim_file_count >= N) {
goto nonsense;
}
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// already exists?
bool orphan = true;
uint32_t wprng = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim[j] == x) {
if (sim_isdirs[j]) {
goto nonsense;
}
orphan = false;
wprng = sim_prngs[j];
break;
}
}
// choose a random seed if we don't exist
if (orphan) {
wprng = TEST_PRNG(&prng);
}
// open in our sim
lfs_size_t j = sim_file_count;
sim_files[j] = malloc(sizeof(sim_file_t));
sim_files[j]->x = x;
sim_files[j]->orphan = orphan;
sim_files[j]->zombie = false;
sim_files[j]->prng = wprng;
sim_file_count++;
// open the actual file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_file_open(&lfs, &sim_files[j]->file, name,
LFS_O_RDWR | LFS_O_CREAT) => 0;
// write some initial data if we don't exist
if (orphan) {
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE)
=> SIZE;
}
// write/rewrite a file?
} else if (op == 1) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng);
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// insert into our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = wprng;
sim_isdirs[k] = false;
}
break;
}
}
// update related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
sim_files[k]->orphan = false;
sim_files[k]->prng = wprng;
}
}
}
// write to the file
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &sim_files[j]->file)
=> (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT;
// close a file?
} else if (op == 2) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
// this doesn't really test anything, but if we don't close
// files eventually everything will end up zombies
// close the file without affected disk
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
// remove from list
free(sim_files[j]);
sim_files[j] = sim_files[sim_file_count-1];
sim_file_count -= 1;
// remove a file?
} else if (op == 3) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_remove(&lfs, name) => 0;
// rename a file?
} else if (op == 4) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
bool isdir = sim_isdirs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// type mismatch?
if (sim_isdirs[k] != isdir) {
goto nonsense;
}
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim[k] = y;
sim_prngs[k] = wprng;
sim_isdirs[k] = isdir;
}
break;
}
}
// update any related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
// move source files
if (sim_files[k]->x == x) {
sim_files[k]->x = y;
// mark target files as zombied
} else if (sim_files[k]->x == y) {
sim_files[k]->zombie = true;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "batman%03x", x);
char new_name[256];
sprintf(new_name, "batman%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
// toss a directory into the mix
} else if (op == 5) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// insert into our sim, use negative numbers for dirs
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
goto nonsense;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = 0;
sim_isdirs[k] = true;
}
break;
}
}
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// make the directory
char name[256];
sprintf(name, "batman%03x", x);
lfsr_mkdir(&lfs, name) => 0;
}
}
// check that disk matches our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS_TYPE_DIR);
} else {
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS_TYPE_DIR);
} else {
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;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim_isdirs[j]) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY)
=> LFS_ERR_ISDIR;
} else {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
}
// check that our file handles match our simulation
for (lfs_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
for (lfs_size_t j = 0; j < sim_file_count; j++) {
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfsr_unmount(&lfs) => 0;
// reset badblock
lfs_emubd_markgood(CFG, badblock) => 0;
}
'''
## Badblock regions
#
# Test with a region of badblocks, this chould cause cascading failures,
# which can be tricky
# B-tree's ridiculous branching factor is great for performance, but it makes
# them a bit of a pain to test, here we test them explicitly
[cases.test_badblocks_region_btree_many]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
# maximize lookahead buffer to avoid alloc scans
defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)'
defines.SEED = 42
fuzz = 'SEED'
in = 'lfs.c'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
lfs_emubd_markbad(CFG, i) => 0;
} else {
lfs_emubd_markbad(CFG, i + BLOCK_COUNT/2) => 0;
}
}
// test creating a btree
lfs_t lfs;
lfs_init(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// create free lookahead
memset(lfs.lookahead.buffer, 0, CFG->lookahead_size);
lfs.lookahead.window = 2;
lfs.lookahead.off = 0;
lfs.lookahead.size = lfs_min(8*CFG->lookahead_size,
CFG->block_count-2);
lfs_alloc_ckpoint(&lfs);
// create a btree
lfsr_btree_t btree = LFSR_BTREE_NULL();
// set up a simulation to compare against
char *sim = malloc(N);
lfs_size_t sim_size = 0;
memset(sim, 0, N);
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1);
// add to btree
lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_DATA, +1,
LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0;
// add to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
sim[bid] = 'a'+(i % 26);
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.weight,
btree.blocks[0],
btree.trunk);
assert(btree.weight == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_lookup(&lfs, &btree, i,
&tag_, &weight_, &data_) => 0;
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_DATA);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_lookup(&lfs, &btree, sim_size,
&tag_, &weight_, &data_) => LFS_ERR_NOENT;
// clean up sim
free(sim);
lfs_deinit(&lfs) => 0;
'''
# with dirs
[cases.test_badblocks_region_dir_many]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (i >= 2) {
lfs_emubd_markbad(CFG, i) => 0;
}
} else {
if (i+BLOCK_COUNT/2 >= 2) {
lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0;
}
}
}
// test creating directories
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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;
'''
# fuzz dirs
[cases.test_badblocks_region_dir_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
defines.OPS = '2*N'
defines.SEED = 42
fuzz = 'SEED'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (i >= 2) {
lfs_emubd_markbad(CFG, i) => 0;
}
} else {
if (i+BLOCK_COUNT/2 >= 2) {
lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0;
}
}
}
// test fuzz with dirs
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose a pseudo-random op, either mkdir, remove, or rename
uint8_t op = TEST_PRNG(&prng) % 3;
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number, truncate to 3 hexadecimals
lfs_size_t x = TEST_PRNG(&prng) % N;
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// do nothing
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
sim_size += 1;
sim[j] = x;
}
break;
}
}
// create a directory here
char name[256];
sprintf(name, "dir%03x", x);
int err = lfsr_mkdir(&lfs, name);
assert(!err || err == LFS_ERR_EXIST);
} else if (op == 1) {
// choose a pseudo-random entry to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
sim_size -= 1;
// remove this directory
char name[256];
sprintf(name, "dir%03x", x);
lfsr_remove(&lfs, name) => 0;
} else {
// choose a pseudo-random entry to rename, and a pseudo-random
// number to rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// already seen and not a noop?
if (k < sim_size && sim[k] == y && x != y) {
// just delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
sim_size -= 1;
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
sim[k] = y;
}
break;
}
}
// rename this directory
char old_name[256];
sprintf(old_name, "dir%03x", x);
char new_name[256];
sprintf(new_name, "dir%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// grm should be zero here
assert(lfs.grm_p[0] == 0);
// test that our directories match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
char name2[256];
sprintf(name2, "dir%03x", sim[j]);
assert(strcmp(info.name, name2) == 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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
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;
}
// clean up sim/lfs
free(sim);
lfsr_unmount(&lfs) => 0;
'''
# with files
[cases.test_badblocks_region_file_many]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
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 = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (i >= 2) {
lfs_emubd_markbad(CFG, i) => 0;
}
} else {
if (i+BLOCK_COUNT/2 >= 2) {
lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0;
}
}
}
// test creating files
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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;
'''
# fuzz files
[cases.test_badblocks_region_file_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (i >= 2) {
lfs_emubd_markbad(CFG, i) => 0;
}
} else {
if (i+BLOCK_COUNT/2 >= 2) {
lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0;
}
}
}
// test fuzz with files
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 3;
// creating a new file?
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// associate each file with a prng that generates its contents
uint32_t wprng = TEST_PRNG(&prng);
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// new prng
sim_prngs[j] = wprng;
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
memmove(&sim_prngs[j+1], &sim_prngs[j],
(sim_size-j)*sizeof(uint32_t));
sim_size += 1;
sim[j] = x;
sim_prngs[j] = wprng;
}
break;
}
}
// create a file here
char name[256];
sprintf(name, "amethyst%03x", x);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 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) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// delete this file
char name[256];
sprintf(name, "amethyst%03x", x);
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) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "amethyst%03x", x);
char new_name[256];
sprintf(new_name, "amethyst%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// check that our files match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
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);
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 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;
// check the file contents
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
# with more complex file writes
[cases.test_badblocks_region_fwrite_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.OPS = 20
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 = [32, 8, 1]
# INIT=0 => no init
# INIT=1 => fill with data
# INIT=2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [false, true]
defines.SEED = 42
fuzz = 'SEED'
if = [
'CHUNK <= SIZE',
# this just saves testing time
'SIZE <= 4*1024*FRAGMENT_SIZE',
]
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (i >= 2) {
lfs_emubd_markbad(CFG, i) => 0;
}
} else {
if (i+BLOCK_COUNT/2 >= 2) {
lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0;
}
}
}
// test with complex file writes
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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 (lfs_size_t i = 0; i < OPS; i++) {
// choose a random location
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
// and a random size, up to the chunk size
lfs_size_t chunk = lfs_min(
TEST_PRNG(&prng) % CHUNK,
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_max(size, off+chunk);
}
// update file
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk;
// sync?
if (SYNC) {
lfsr_file_sync(&lfs, &file) => 0;
}
}
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// check our file with stat
struct lfs_info info;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == size);
// and with dir read
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == size);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// try reading our file
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
// is size correct?
lfsr_file_size(&lfs, &file) => size;
// try reading
uint8_t rbuf[2*SIZE];
memset(rbuf, 0xaa, 2*SIZE);
lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size;
// does our file match our simulation?
assert(memcmp(rbuf, sim, size) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
# with orphans, zombies, etc
[cases.test_badblocks_region_orphanzombie_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (i >= 2) {
lfs_emubd_markbad(CFG, i) => 0;
}
} else {
if (i+BLOCK_COUNT/2 >= 2) {
lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0;
}
}
}
// test with orphans, zombies, etc
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
typedef struct sim_file {
lfs_size_t x;
bool orphan;
bool zombie;
uint32_t prng;
lfsr_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
nonsense:;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 5;
// open a new file?
if (op == 0) {
if (sim_file_count >= N) {
goto nonsense;
}
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// already exists?
bool orphan = true;
uint32_t wprng = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim[j] == x) {
orphan = false;
wprng = sim_prngs[j];
break;
}
}
// choose a random seed if we don't exist
if (orphan) {
wprng = TEST_PRNG(&prng);
}
// open in our sim
lfs_size_t j = sim_file_count;
sim_files[j] = malloc(sizeof(sim_file_t));
sim_files[j]->x = x;
sim_files[j]->orphan = orphan;
sim_files[j]->zombie = false;
sim_files[j]->prng = wprng;
sim_file_count++;
// open the actual file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_file_open(&lfs, &sim_files[j]->file, name,
LFS_O_RDWR | LFS_O_CREAT) => 0;
// write some initial data if we don't exist
if (orphan) {
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
}
// write/rewrite a file?
} else if (op == 1) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng);
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// insert into our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = wprng;
}
break;
}
}
// update related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
sim_files[k]->orphan = false;
sim_files[k]->prng = wprng;
}
}
}
// write to the file
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &sim_files[j]->file)
=> (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT;
// close a file?
} else if (op == 2) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
// this doesn't really test anything, but if we don't close
// files eventually everything will end up zombies
// close the file without affected disk
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
// remove from list
free(sim_files[j]);
sim_files[j] = sim_files[sim_file_count-1];
sim_file_count -= 1;
// remove a file?
} else if (op == 3) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_remove(&lfs, name) => 0;
// rename a file?
} else if (op == 4) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
// update any related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
// move source files
if (sim_files[k]->x == x) {
sim_files[k]->x = y;
// mark target files as zombied
} else if (sim_files[k]->x == y) {
sim_files[k]->zombie = true;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "batman%03x", x);
char new_name[256];
sprintf(new_name, "batman%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
// check that disk matches our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
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);
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 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;
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// check that our file handles match our simulation
for (lfs_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
for (lfs_size_t j = 0; j < sim_file_count; j++) {
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfsr_unmount(&lfs) => 0;
'''
# with orphans, zombies, dirs, etc
[cases.test_badblocks_region_orphanzombiedir_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (i >= 2) {
lfs_emubd_markbad(CFG, i) => 0;
}
} else {
if (i+BLOCK_COUNT/2 >= 2) {
lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0;
}
}
}
// test with orphans, zombies, dirs, etc
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
bool *sim_isdirs = malloc(N*sizeof(bool));
lfs_size_t sim_size = 0;
typedef struct sim_file {
lfs_size_t x;
bool orphan;
bool zombie;
uint32_t prng;
lfsr_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
nonsense:;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 8;
// open a new file?
if (op == 0) {
if (sim_file_count >= N) {
goto nonsense;
}
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// already exists?
bool orphan = true;
uint32_t wprng = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim[j] == x) {
if (sim_isdirs[j]) {
goto nonsense;
}
orphan = false;
wprng = sim_prngs[j];
break;
}
}
// choose a random seed if we don't exist
if (orphan) {
wprng = TEST_PRNG(&prng);
}
// open in our sim
lfs_size_t j = sim_file_count;
sim_files[j] = malloc(sizeof(sim_file_t));
sim_files[j]->x = x;
sim_files[j]->orphan = orphan;
sim_files[j]->zombie = false;
sim_files[j]->prng = wprng;
sim_file_count++;
// open the actual file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_file_open(&lfs, &sim_files[j]->file, name,
LFS_O_RDWR | LFS_O_CREAT) => 0;
// write some initial data if we don't exist
if (orphan) {
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
}
// write/rewrite a file?
} else if (op == 1) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng);
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// insert into our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = wprng;
sim_isdirs[k] = false;
}
break;
}
}
// update related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
sim_files[k]->orphan = false;
sim_files[k]->prng = wprng;
}
}
}
// write to the file
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &sim_files[j]->file)
=> (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT;
// close a file?
} else if (op == 2) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
// this doesn't really test anything, but if we don't close
// files eventually everything will end up zombies
// close the file without affected disk
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
// remove from list
free(sim_files[j]);
sim_files[j] = sim_files[sim_file_count-1];
sim_file_count -= 1;
// remove a file?
} else if (op == 3) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_remove(&lfs, name) => 0;
// rename a file?
} else if (op == 4) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
bool isdir = sim_isdirs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// type mismatch?
if (sim_isdirs[k] != isdir) {
goto nonsense;
}
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim[k] = y;
sim_prngs[k] = wprng;
sim_isdirs[k] = isdir;
}
break;
}
}
// update any related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
// move source files
if (sim_files[k]->x == x) {
sim_files[k]->x = y;
// mark target files as zombied
} else if (sim_files[k]->x == y) {
sim_files[k]->zombie = true;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "batman%03x", x);
char new_name[256];
sprintf(new_name, "batman%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
// toss a directory into the mix
} else if (op == 5) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// insert into our sim, use negative numbers for dirs
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
goto nonsense;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = 0;
sim_isdirs[k] = true;
}
break;
}
}
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// make the directory
char name[256];
sprintf(name, "batman%03x", x);
lfsr_mkdir(&lfs, name) => 0;
}
}
// check that disk matches our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS_TYPE_DIR);
} else {
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS_TYPE_DIR);
} else {
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;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim_isdirs[j]) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_ISDIR;
} else {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
}
// check that our file handles match our simulation
for (lfs_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
for (lfs_size_t j = 0; j < sim_file_count; j++) {
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfsr_unmount(&lfs) => 0;
'''
## Alternating badblocks
#
# Test alternating badblocks, this can be difficult for pair allocations
# B-tree's ridiculous branching factor is great for performance, but it makes
# them a bit of a pain to test, here we test them explicitly
[cases.test_badblocks_alternating_btree_many]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
# maximize lookahead buffer to avoid alloc scans
defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)'
defines.SEED = 42
fuzz = 'SEED'
in = 'lfs.c'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
lfs_emubd_markbad(CFG, 2*i+0) => 0;
} else {
lfs_emubd_markbad(CFG, 2*i+1) => 0;
}
}
// test creating a btree
lfs_t lfs;
lfs_init(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// create free lookahead
memset(lfs.lookahead.buffer, 0, CFG->lookahead_size);
lfs.lookahead.window = 2;
lfs.lookahead.off = 0;
lfs.lookahead.size = lfs_min(8*CFG->lookahead_size,
CFG->block_count-2);
lfs_alloc_ckpoint(&lfs);
// create a btree
lfsr_btree_t btree = LFSR_BTREE_NULL();
// set up a simulation to compare against
char *sim = malloc(N);
lfs_size_t sim_size = 0;
memset(sim, 0, N);
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1);
// add to btree
lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_DATA, +1,
LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0;
// add to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
sim[bid] = 'a'+(i % 26);
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.weight,
btree.blocks[0],
btree.trunk);
assert(btree.weight == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_lookup(&lfs, &btree, i,
&tag_, &weight_, &data_) => 0;
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_DATA);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_lookup(&lfs, &btree, sim_size,
&tag_, &weight_, &data_) => LFS_ERR_NOENT;
// clean up sim
free(sim);
lfs_deinit(&lfs) => 0;
'''
# with dirs
[cases.test_badblocks_alternating_dir_many]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (2*i+0 >= 2) {
lfs_emubd_markbad(CFG, 2*i+0) => 0;
}
} else {
if (2*i+1 >= 2) {
lfs_emubd_markbad(CFG, 2*i+1) => 0;
}
}
}
// test creating directories
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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;
'''
# fuzz dirs
[cases.test_badblocks_alternating_dir_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
defines.OPS = '2*N'
defines.SEED = 42
fuzz = 'SEED'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (2*i+0 >= 2) {
lfs_emubd_markbad(CFG, 2*i+0) => 0;
}
} else {
if (2*i+1 >= 2) {
lfs_emubd_markbad(CFG, 2*i+1) => 0;
}
}
}
// test fuzz with dirs
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose a pseudo-random op, either mkdir, remove, or rename
uint8_t op = TEST_PRNG(&prng) % 3;
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number, truncate to 3 hexadecimals
lfs_size_t x = TEST_PRNG(&prng) % N;
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// do nothing
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
sim_size += 1;
sim[j] = x;
}
break;
}
}
// create a directory here
char name[256];
sprintf(name, "dir%03x", x);
int err = lfsr_mkdir(&lfs, name);
assert(!err || err == LFS_ERR_EXIST);
} else if (op == 1) {
// choose a pseudo-random entry to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
sim_size -= 1;
// remove this directory
char name[256];
sprintf(name, "dir%03x", x);
lfsr_remove(&lfs, name) => 0;
} else {
// choose a pseudo-random entry to rename, and a pseudo-random
// number to rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// already seen and not a noop?
if (k < sim_size && sim[k] == y && x != y) {
// just delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
sim_size -= 1;
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
sim[k] = y;
}
break;
}
}
// rename this directory
char old_name[256];
sprintf(old_name, "dir%03x", x);
char new_name[256];
sprintf(new_name, "dir%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// grm should be zero here
assert(lfs.grm_p[0] == 0);
// test that our directories match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
char name2[256];
sprintf(name2, "dir%03x", sim[j]);
assert(strcmp(info.name, name2) == 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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
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;
}
// clean up sim/lfs
free(sim);
lfsr_unmount(&lfs) => 0;
'''
# with files
[cases.test_badblocks_alternating_file_many]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
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 = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (2*i+0 >= 2) {
lfs_emubd_markbad(CFG, 2*i+0) => 0;
}
} else {
if (2*i+1 >= 2) {
lfs_emubd_markbad(CFG, 2*i+1) => 0;
}
}
}
// test creating files
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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;
'''
# fuzz files
[cases.test_badblocks_alternating_file_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (2*i+0 >= 2) {
lfs_emubd_markbad(CFG, 2*i+0) => 0;
}
} else {
if (2*i+1 >= 2) {
lfs_emubd_markbad(CFG, 2*i+1) => 0;
}
}
}
// test fuzz with files
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 3;
// creating a new file?
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// associate each file with a prng that generates its contents
uint32_t wprng = TEST_PRNG(&prng);
// insert into our sim
for (lfs_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// new prng
sim_prngs[j] = wprng;
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs_size_t));
memmove(&sim_prngs[j+1], &sim_prngs[j],
(sim_size-j)*sizeof(uint32_t));
sim_size += 1;
sim[j] = x;
sim_prngs[j] = wprng;
}
break;
}
}
// create a file here
char name[256];
sprintf(name, "amethyst%03x", x);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 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) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// delete this file
char name[256];
sprintf(name, "amethyst%03x", x);
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) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "amethyst%03x", x);
char new_name[256];
sprintf(new_name, "amethyst%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// check that our files match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
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);
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 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;
// check the file contents
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
# with more complex file writes
[cases.test_badblocks_alternating_fwrite_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.OPS = 20
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 = [32, 8, 1]
# INIT=0 => no init
# INIT=1 => fill with data
# INIT=2 => truncate to size
defines.INIT = [0, 1, 2]
defines.SYNC = [false, true]
defines.SEED = 42
fuzz = 'SEED'
if = [
'CHUNK <= SIZE',
# this just saves testing time
'SIZE <= 4*1024*FRAGMENT_SIZE',
]
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (2*i+0 >= 2) {
lfs_emubd_markbad(CFG, 2*i+0) => 0;
}
} else {
if (2*i+1 >= 2) {
lfs_emubd_markbad(CFG, 2*i+1) => 0;
}
}
}
// test with complex file writes
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
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 (lfs_size_t i = 0; i < OPS; i++) {
// choose a random location
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
// and a random size, up to the chunk size
lfs_size_t chunk = lfs_min(
(TEST_PRNG(&prng) % (CHUNK+1-1)) + 1,
SIZE - off);
// update sim
for (lfs_size_t j = 0; j < chunk; j++) {
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
}
size = lfs_max(size, off+chunk);
// update file
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk;
// sync?
if (SYNC) {
lfsr_file_sync(&lfs, &file) => 0;
}
}
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
}
// check our file with stat
struct lfs_info info;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == size);
// and with dir read
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == size);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// try reading our file
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
// is size correct?
lfsr_file_size(&lfs, &file) => size;
// try reading
uint8_t rbuf[2*SIZE];
memset(rbuf, 0xaa, 2*SIZE);
lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size;
// does our file match our simulation?
assert(memcmp(rbuf, sim, size) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
# with orphans, zombies, etc
[cases.test_badblocks_alternating_orphanzombie_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (2*i+0 >= 2) {
lfs_emubd_markbad(CFG, 2*i+0) => 0;
}
} else {
if (2*i+1 >= 2) {
lfs_emubd_markbad(CFG, 2*i+1) => 0;
}
}
}
// test with orphans, zombies, etc
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
typedef struct sim_file {
lfs_size_t x;
bool orphan;
bool zombie;
uint32_t prng;
lfsr_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
nonsense:;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 5;
// open a new file?
if (op == 0) {
if (sim_file_count >= N) {
goto nonsense;
}
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// already exists?
bool orphan = true;
uint32_t wprng = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim[j] == x) {
orphan = false;
wprng = sim_prngs[j];
break;
}
}
// choose a random seed if we don't exist
if (orphan) {
wprng = TEST_PRNG(&prng);
}
// open in our sim
lfs_size_t j = sim_file_count;
sim_files[j] = malloc(sizeof(sim_file_t));
sim_files[j]->x = x;
sim_files[j]->orphan = orphan;
sim_files[j]->zombie = false;
sim_files[j]->prng = wprng;
sim_file_count++;
// open the actual file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_file_open(&lfs, &sim_files[j]->file, name,
LFS_O_RDWR | LFS_O_CREAT) => 0;
// write some initial data if we don't exist
if (orphan) {
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
}
// write/rewrite a file?
} else if (op == 1) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng);
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// insert into our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = wprng;
}
break;
}
}
// update related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
sim_files[k]->orphan = false;
sim_files[k]->prng = wprng;
}
}
}
// write to the file
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &sim_files[j]->file)
=> (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT;
// close a file?
} else if (op == 2) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
// this doesn't really test anything, but if we don't close
// files eventually everything will end up zombies
// close the file without affected disk
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
// remove from list
free(sim_files[j]);
sim_files[j] = sim_files[sim_file_count-1];
sim_file_count -= 1;
// remove a file?
} else if (op == 3) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_remove(&lfs, name) => 0;
// rename a file?
} else if (op == 4) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
// update any related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
// move source files
if (sim_files[k]->x == x) {
sim_files[k]->x = y;
// mark target files as zombied
} else if (sim_files[k]->x == y) {
sim_files[k]->zombie = true;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "batman%03x", x);
char new_name[256];
sprintf(new_name, "batman%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
}
// check that disk matches our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
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);
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 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;
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
// check that our file handles match our simulation
for (lfs_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
for (lfs_size_t j = 0; j < sim_file_count; j++) {
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfsr_unmount(&lfs) => 0;
'''
# with orphans, zombies, dirs, etc
[cases.test_badblocks_alternating_orphanzombiedir_fuzz]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
'LFS_EMUBD_BADBLOCK_PROGFLIP',
]
# we need prog checking to detect read errors
defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
defines.MIRROR = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = '2*N'
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 42
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// test a large region of bad blocks
for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) {
// mark our badblock as bad
if (!MIRROR) {
if (2*i+0 >= 2) {
lfs_emubd_markbad(CFG, 2*i+0) => 0;
}
} else {
if (2*i+1 >= 2) {
lfs_emubd_markbad(CFG, 2*i+1) => 0;
}
}
}
// test with orphans, zombies, dirs, etc
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
bool *sim_isdirs = malloc(N*sizeof(bool));
lfs_size_t sim_size = 0;
typedef struct sim_file {
lfs_size_t x;
bool orphan;
bool zombie;
uint32_t prng;
lfsr_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
nonsense:;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 8;
// open a new file?
if (op == 0) {
if (sim_file_count >= N) {
goto nonsense;
}
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// already exists?
bool orphan = true;
uint32_t wprng = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim[j] == x) {
if (sim_isdirs[j]) {
goto nonsense;
}
orphan = false;
wprng = sim_prngs[j];
break;
}
}
// choose a random seed if we don't exist
if (orphan) {
wprng = TEST_PRNG(&prng);
}
// open in our sim
lfs_size_t j = sim_file_count;
sim_files[j] = malloc(sizeof(sim_file_t));
sim_files[j]->x = x;
sim_files[j]->orphan = orphan;
sim_files[j]->zombie = false;
sim_files[j]->prng = wprng;
sim_file_count++;
// open the actual file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_file_open(&lfs, &sim_files[j]->file, name,
LFS_O_RDWR | LFS_O_CREAT) => 0;
// write some initial data if we don't exist
if (orphan) {
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
}
// write/rewrite a file?
} else if (op == 1) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng);
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// insert into our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = wprng;
sim_isdirs[k] = false;
}
break;
}
}
// update related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
sim_files[k]->orphan = false;
sim_files[k]->prng = wprng;
}
}
}
// write to the file
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &sim_files[j]->file)
=> (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT;
// close a file?
} else if (op == 2) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
// this doesn't really test anything, but if we don't close
// files eventually everything will end up zombies
// close the file without affected disk
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
// remove from list
free(sim_files[j]);
sim_files[j] = sim_files[sim_file_count-1];
sim_file_count -= 1;
// remove a file?
} else if (op == 3) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
lfsr_remove(&lfs, name) => 0;
// rename a file?
} else if (op == 4) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng) % N;
uint32_t wprng = sim_prngs[j];
bool isdir = sim_isdirs[j];
// update our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// type mismatch?
if (sim_isdirs[k] != isdir) {
goto nonsense;
}
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim[k] = y;
sim_prngs[k] = wprng;
sim_isdirs[k] = isdir;
}
break;
}
}
// update any related sim files
for (lfs_size_t k = 0; k < sim_file_count; k++) {
// move source files
if (sim_files[k]->x == x) {
sim_files[k]->x = y;
// mark target files as zombied
} else if (sim_files[k]->x == y) {
sim_files[k]->zombie = true;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "batman%03x", x);
char new_name[256];
sprintf(new_name, "batman%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
// toss a directory into the mix
} else if (op == 5) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
// insert into our sim, use negative numbers for dirs
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
goto nonsense;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = 0;
sim_isdirs[k] = true;
}
break;
}
}
// mark any related sim files as zombied
for (lfs_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// make the directory
char name[256];
sprintf(name, "batman%03x", x);
lfsr_mkdir(&lfs, name) => 0;
}
}
// check that disk matches our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS_TYPE_DIR);
} else {
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS_TYPE_DIR);
} else {
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;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim_isdirs[j]) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_ISDIR;
} else {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
}
// check that our file handles match our simulation
for (lfs_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
for (lfs_size_t j = 0; j < sim_file_count; j++) {
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfsr_unmount(&lfs) => 0;
'''
## other corner cases
# test formatting with 0 or 1 bad, this should just error
[cases.test_badblocks_mrootanchor_format]
defines.BADBLOCKS = [0x1, 0x2, 0x3]
defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_PROGERROR',
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
]
code = '''
if (BADBLOCKS & 0x1) {
lfs_emubd_markbad(CFG, 0) => 0;
}
if (BADBLOCKS & 0x2) {
lfs_emubd_markbad(CFG, 1) => 0;
}
lfs_t lfs;
lfsr_format(&lfs, CFG) => LFS_ERR_CORRUPT;
'''
# test blocks 0 or 1 going bad, this should just error
[cases.test_badblocks_mrootanchor_wear]
defines.BADBLOCKS = [0x1, 0x2]
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.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
if (BADBLOCKS & 0x1) {
lfs_emubd_markbad(CFG, 0) => 0;
}
if (BADBLOCKS & 0x2) {
lfs_emubd_markbad(CFG, 1) => 0;
}
lfsr_mount(&lfs,
LFS_M_RDWR
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
CFG) => 0;
for (lfs_size_t i = 0;; i++) {
// this should eventually fail
assert(i <= BLOCK_COUNT);
int err = lfsr_mkdir(&lfs, "hi");
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
break;
}
err = lfsr_remove(&lfs, "hi");
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
break;
}
}
lfsr_unmount(&lfs) => 0;
'''
## bad blocks with block cycles should be tested in test_relocations
#if = '(int32_t)BLOCK_CYCLES == -1'
#
#[cases.test_badblocks_single]
#defines.BLOCK_COUNT = 256 # small bd so test runs faster
#defines.ERASE_CYCLES = 0xffffffff
#defines.ERASE_VALUE = [0x00, 0xff, -1]
#defines.BADBLOCK_BEHAVIOR = [
# 'LFS_EMUBD_BADBLOCK_PROGERROR',
# 'LFS_EMUBD_BADBLOCK_ERASEERROR',
# 'LFS_EMUBD_BADBLOCK_READERROR',
# 'LFS_EMUBD_BADBLOCK_PROGNOOP',
# 'LFS_EMUBD_BADBLOCK_ERASENOOP',
#]
#defines.NAMEMULT = 64
#defines.FILEMULT = 1
#code = '''
# for (lfs_block_t badblock = 2; badblock < BLOCK_COUNT; badblock++) {
# lfs_emubd_setwear(cfg, badblock-1, 0) => 0;
# lfs_emubd_setwear(cfg, badblock, 0xffffffff) => 0;
#
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 1; i < 10; i++) {
# uint8_t buffer[1024];
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j] = '0'+i;
# }
# buffer[NAMEMULT] = '\0';
# lfs_mkdir(&lfs, (char*)buffer) => 0;
#
# buffer[NAMEMULT] = '/';
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j+NAMEMULT+1] = '0'+i;
# }
# buffer[2*NAMEMULT+1] = '\0';
# lfs_file_t file;
# lfs_file_open(&lfs, &file, (char*)buffer,
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
#
# lfs_size_t size = NAMEMULT;
# for (int j = 0; j < i*FILEMULT; j++) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
#
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 1; i < 10; i++) {
# uint8_t buffer[1024];
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j] = '0'+i;
# }
# buffer[NAMEMULT] = '\0';
# struct lfs_info info;
# lfs_stat(&lfs, (char*)buffer, &info) => 0;
# info.type => LFS_TYPE_DIR;
#
# buffer[NAMEMULT] = '/';
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j+NAMEMULT+1] = '0'+i;
# }
# buffer[2*NAMEMULT+1] = '\0';
# lfs_file_t file;
# lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0;
#
# int size = NAMEMULT;
# for (int j = 0; j < i*FILEMULT; j++) {
# uint8_t rbuffer[1024];
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
# memcmp(buffer, rbuffer, size) => 0;
# }
#
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
# }
#'''
#
#[cases.test_badblocks_region_corruption] # (causes cascading failures)
#defines.BLOCK_COUNT = 256 # small bd so test runs faster
#defines.ERASE_CYCLES = 0xffffffff
#defines.ERASE_VALUE = [0x00, 0xff, -1]
#defines.BADBLOCK_BEHAVIOR = [
# 'LFS_EMUBD_BADBLOCK_PROGERROR',
# 'LFS_EMUBD_BADBLOCK_ERASEERROR',
# 'LFS_EMUBD_BADBLOCK_READERROR',
# 'LFS_EMUBD_BADBLOCK_PROGNOOP',
# 'LFS_EMUBD_BADBLOCK_ERASENOOP',
#]
#defines.NAMEMULT = 64
#defines.FILEMULT = 1
#code = '''
# for (lfs_block_t i = 0; i < (BLOCK_COUNT-2)/2; i++) {
# lfs_emubd_setwear(cfg, i+2, 0xffffffff) => 0;
# }
#
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 1; i < 10; i++) {
# uint8_t buffer[1024];
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j] = '0'+i;
# }
# buffer[NAMEMULT] = '\0';
# lfs_mkdir(&lfs, (char*)buffer) => 0;
#
# buffer[NAMEMULT] = '/';
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j+NAMEMULT+1] = '0'+i;
# }
# buffer[2*NAMEMULT+1] = '\0';
# lfs_file_t file;
# lfs_file_open(&lfs, &file, (char*)buffer,
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
#
# lfs_size_t size = NAMEMULT;
# for (int j = 0; j < i*FILEMULT; j++) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
#
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 1; i < 10; i++) {
# uint8_t buffer[1024];
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j] = '0'+i;
# }
# buffer[NAMEMULT] = '\0';
# struct lfs_info info;
# lfs_stat(&lfs, (char*)buffer, &info) => 0;
# info.type => LFS_TYPE_DIR;
#
# buffer[NAMEMULT] = '/';
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j+NAMEMULT+1] = '0'+i;
# }
# buffer[2*NAMEMULT+1] = '\0';
# lfs_file_t file;
# lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0;
#
# lfs_size_t size = NAMEMULT;
# for (int j = 0; j < i*FILEMULT; j++) {
# uint8_t rbuffer[1024];
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
# memcmp(buffer, rbuffer, size) => 0;
# }
#
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_badblocks_alternating_corruption] # (causes cascading failures)
#defines.BLOCK_COUNT = 256 # small bd so test runs faster
#defines.ERASE_CYCLES = 0xffffffff
#defines.ERASE_VALUE = [0x00, 0xff, -1]
#defines.BADBLOCK_BEHAVIOR = [
# 'LFS_EMUBD_BADBLOCK_PROGERROR',
# 'LFS_EMUBD_BADBLOCK_ERASEERROR',
# 'LFS_EMUBD_BADBLOCK_READERROR',
# 'LFS_EMUBD_BADBLOCK_PROGNOOP',
# 'LFS_EMUBD_BADBLOCK_ERASENOOP',
#]
#defines.NAMEMULT = 64
#defines.FILEMULT = 1
#code = '''
# for (lfs_block_t i = 0; i < (BLOCK_COUNT-2)/2; i++) {
# lfs_emubd_setwear(cfg, (2*i) + 2, 0xffffffff) => 0;
# }
#
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 1; i < 10; i++) {
# uint8_t buffer[1024];
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j] = '0'+i;
# }
# buffer[NAMEMULT] = '\0';
# lfs_mkdir(&lfs, (char*)buffer) => 0;
#
# buffer[NAMEMULT] = '/';
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j+NAMEMULT+1] = '0'+i;
# }
# buffer[2*NAMEMULT+1] = '\0';
# lfs_file_t file;
# lfs_file_open(&lfs, &file, (char*)buffer,
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
#
# lfs_size_t size = NAMEMULT;
# for (int j = 0; j < i*FILEMULT; j++) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
#
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 1; i < 10; i++) {
# uint8_t buffer[1024];
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j] = '0'+i;
# }
# buffer[NAMEMULT] = '\0';
# struct lfs_info info;
# lfs_stat(&lfs, (char*)buffer, &info) => 0;
# info.type => LFS_TYPE_DIR;
#
# buffer[NAMEMULT] = '/';
# for (int j = 0; j < NAMEMULT; j++) {
# buffer[j+NAMEMULT+1] = '0'+i;
# }
# buffer[2*NAMEMULT+1] = '\0';
# lfs_file_t file;
# lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0;
#
# lfs_size_t size = NAMEMULT;
# for (int j = 0; j < i*FILEMULT; j++) {
# uint8_t rbuffer[1024];
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
# memcmp(buffer, rbuffer, size) => 0;
# }
#
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''
#
## other corner cases
#[cases.test_badblocks_superblocks] # (corrupt 1 or 0)
#defines.ERASE_CYCLES = 0xffffffff
#defines.ERASE_VALUE = [0x00, 0xff, -1]
#defines.BADBLOCK_BEHAVIOR = [
# 'LFS_EMUBD_BADBLOCK_PROGERROR',
# 'LFS_EMUBD_BADBLOCK_ERASEERROR',
# 'LFS_EMUBD_BADBLOCK_READERROR',
# 'LFS_EMUBD_BADBLOCK_PROGNOOP',
# 'LFS_EMUBD_BADBLOCK_ERASENOOP',
#]
#code = '''
# lfs_emubd_setwear(cfg, 0, 0xffffffff) => 0;
# lfs_emubd_setwear(cfg, 1, 0xffffffff) => 0;
#
# lfs_t lfs;
# lfs_format(&lfs, cfg) => LFS_ERR_NOSPC;
# lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
#'''