Files
littlefs/tests/test_relocations.toml
T
Christopher Haster 3b33c33339 Added pseudo-stateless *_pl_fuzz tests
These provide useful file powerloss testing that scales linearly as long
as progress can be made. They can still struggle a bit, especially with
relocations which often fail to make progress, but they are _much_ better
than the O(n^2) simulation-based fuzz tests:

- test_files_pl_fuzz - 258734 pls
- test_relocations_pl_fuzz - 928638 pls

Our current problem with simulation-based fuzz testing is that we lose
the simulation on powerloss. We could brute force this, repeatedly
rerunning the simulation until it succeeds, but this grows O(n^2) with
our linear powerloss heuristic.

To avoid this, test_*_pl_fuzz doesn't bother with a simulation, instead
relying on internal asserts to catch bugs. This is less rigorous, but
realistically probably going to catch any powerloss related issues.

Some notes:

- We need to store some state on disk. If we don't we will still end up
  with O(n^2) behavior because we simply don't know how many operations
  we've accomplished so far.

- Since we rely on file operations to store our test state, this makes
  this approach incompatible with the dir tests, which assume file
  operations may not yet be implemented.

  We still use O(n^2) powerloss testing in test_dirs, just with a small
  number of directories.

- It's tempting to try to store a full simulation on disk. But you
  would quickly run into atomicity issues with the simulation itself.
  Powerloss resilience is tricky!

- We can at least store a checksum in the files (currently just mod 26)
  to check that the file itself was not corrupted. This doesn't protect
  against swapped data though.

---

Also, a bit of a tangent, but I needed to add -Wno-format-overflow to
the test flags to avoid an annoying invalid format-overlow warning:

  struct lfs_info info;
  char name[256];
  if (strlen(info.name) < 100) { // can't overflow!?
      sprintf(name, "test/%s", info.name); // <--
  }

  warning: '%s' directive writing up to 255 bytes into a region of size
  251 [-Wformat-overflow=]

This seems like a GCC bug, because as far as I can tell there is no way
to signal or hint that the size is in bounds without just disabling the
warning completely...
2024-05-27 23:08:05 -05:00

1694 lines
58 KiB
TOML

# Tests over block relocations and wear-leveling
after = [
'test_mtree',
'test_dirs',
'test_files',
'test_forphans',
'test_alloc'
]
# Note that most of the delicate relocation operations are already tested
# in test_mtree. This mostly just covers high-level operations with
# relatively aggressive wear-leveling.
# dirs + relocations may create problems for gstate
[cases.test_relocations_dir_fuzz]
defines.BLOCK_RECYCLES = [-1, 5, 1, 0]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
defines.OPS = 1024
defines.REMOUNT = [false, true]
defines.SEED = 'range(10)'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, 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;
}
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, 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;
'''
# files + relocations may create problems for shrubs
[cases.test_relocations_file_fuzz]
defines.BLOCK_RECYCLES = [-1, 5, 1, 0]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.SEED = 'range(10)'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, 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;
}
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, 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;
'''
# open files + relocations may create problems for orphans/zombies
[cases.test_relocations_orphanzombie_fuzz]
defines.BLOCK_RECYCLES = [-1, 5, 1, 0]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 'range(10)'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, 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;
'''
# open files + dirs + relocations can cause so many problems it's not worth
# listing them
[cases.test_relocations_orphanzombiedir_fuzz]
defines.BLOCK_RECYCLES = [-1, 5, 1, 0]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 'range(10)'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, 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;
'''
# and don't forget potential powerloss problems
#
# Under powerloss, we can't really keep track of a sim reliably/
# efficiently, instead just do random operations, store a counter in a
# special file so we know how much progress has been made, and hope for
# the best. Most likely an internal assert will trigger if anything goes
# wrong.
#
[cases.test_relocations_pl_fuzz]
defines.BLOCK_RECYCLES = [-1, 5, 1, 0]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 256
defines.SIZE = [
'0',
'FBUFFER_SIZE/2',
'2*FBUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.REMOUNT = [false, true]
defines.SEED = 'range(10)'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
reentrant = true
code = '''
// format once per test
lfs_t lfs;
int err = lfsr_mount(&lfs, CFG);
if (err) {
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// keep some test state on disk to survive powerloss
typedef struct fuzz_state {
lfs_size_t i;
uint32_t prng;
} fuzz_state_t;
fuzz_state_t state = {.i = 0, .prng = SEED};
lfsr_file_t state_file;
lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDWR | LFS_O_CREAT) => 0;
lfs_ssize_t d = lfsr_file_read(&lfs, &state_file, &state, sizeof(state));
assert(d == 0 || d == sizeof(state));
// keep test files in a separate directory
err = lfsr_mkdir(&lfs, "test");
assert(!err || err == LFS_ERR_EXIST);
uint32_t prng = state.prng;
for (lfs_size_t i = state.i; i < OPS; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 3;
// how many files do we have?
lfs_size_t count = 0;
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "test") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
while (true) {
int err = lfsr_dir_read(&lfs, &dir, &info);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("amethyst..."));
assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
count++;
}
lfsr_dir_close(&lfs, &dir) => 0;
// creating a new file?
if (op == 0 || count == 0) {
// choose a pseudo-random number
lfs_size_t x = TEST_PRNG(&prng) % N;
uint32_t wprng = TEST_PRNG(&prng);
// create a file here
char name[256];
sprintf(name, "test/amethyst%03x", x);
uint8_t wbuf[SIZE];
uint8_t ck = 0;
for (lfs_size_t j = 0; j < SIZE-1; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
ck = (ck + (wbuf[j] - 'a')) % 26;
}
// make the sum equal to 'a' mod 26
if (SIZE > 0) {
wbuf[SIZE-1] = 'a' + ((26 - ck) % 26);
}
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// deleting a file?
} else if (op == 1) {
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng) % count;
// find the file
lfsr_dir_open(&lfs, &dir, "test") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= j; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
// delete this file
char name[256];
assert(strlen(info.name) == strlen("amethyst..."));
sprintf(name, "test/%s", info.name);
lfsr_remove(&lfs, name) => 0;
// renaming a file?
} else {
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng) % count;
lfs_size_t y = TEST_PRNG(&prng) % N;
// find the file
lfsr_dir_open(&lfs, &dir, "test") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= j; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
// rename this file
char old_name[256];
assert(strlen(info.name) == strlen("amethyst..."));
sprintf(old_name, "test/%s", info.name);
char new_name[256];
sprintf(new_name, "test/amethyst%03x", y);
lfsr_rename(&lfs, old_name, new_name) => 0;
}
// update our state file
state.i = i;
state.prng = prng;
lfsr_file_rewind(&lfs, &state_file) => 0;
lfsr_file_write(&lfs, &state_file, &state, sizeof(state))
=> sizeof(state);
lfsr_file_sync(&lfs, &state_file) => 0;
}
// go ahead and close our state file in case we remount
lfsr_file_close(&lfs, &state_file) => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that things look more-or-less ok
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "test") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
while (true) {
int err = lfsr_dir_read(&lfs, &dir, &info);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("amethyst..."));
assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// at least try to read the files
char name[256];
sprintf(name, "test/%s", info.name);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
// sum should be equal to 'a' mod 26
uint8_t ck = 0;
for (lfs_size_t j = 0; j < SIZE; j++) {
ck = (ck + (rbuf[j] - 'a')) % 26;
}
assert(ck == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
# TODO, but this needs bad blocks
## test that wear-leveling converges to a more-or-less even distribution
#[cases.test_relocations_distribution_dirs]
#[cases.test_relocations_distribution_files]
#[cases.test_relocations_distribution_mixed]
## specific corner cases worth explicitly testing for
#[cases.test_relocations_dangling_split_dir]
#defines.ITERATIONS = 20
#defines.COUNT = 10
#defines.BLOCK_CYCLES = [8, 1]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# // fill up filesystem so only ~16 blocks are left
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0;
# uint8_t buffer[512];
# memset(buffer, 0, 512);
# while (BLOCK_COUNT - lfs_fs_size(&lfs) > 16) {
# lfs_file_write(&lfs, &file, buffer, 512) => 512;
# }
# lfs_file_close(&lfs, &file) => 0;
# // make a child dir to use in bounded space
# lfs_mkdir(&lfs, "child") => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (unsigned j = 0; j < ITERATIONS; j++) {
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
# lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0;
# lfs_file_close(&lfs, &file) => 0;
# }
#
# lfs_dir_t dir;
# struct lfs_info info;
# lfs_dir_open(&lfs, &dir, "child") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "test%03d_loooooooooooooooooong_name", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# strcmp(info.name, path) => 0;
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
#
# if (j == (unsigned)ITERATIONS-1) {
# break;
# }
#
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
# lfs_remove(&lfs, path) => 0;
# }
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# struct lfs_info info;
# lfs_dir_open(&lfs, &dir, "child") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "test%03d_loooooooooooooooooong_name", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# strcmp(info.name, path) => 0;
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
# lfs_remove(&lfs, path) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_relocations_outdated_head]
#defines.ITERATIONS = 20
#defines.COUNT = 10
#defines.BLOCK_CYCLES = [8, 1]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# // fill up filesystem so only ~16 blocks are left
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0;
# uint8_t buffer[512];
# memset(buffer, 0, 512);
# while (BLOCK_COUNT - lfs_fs_size(&lfs) > 16) {
# lfs_file_write(&lfs, &file, buffer, 512) => 512;
# }
# lfs_file_close(&lfs, &file) => 0;
# // make a child dir to use in bounded space
# lfs_mkdir(&lfs, "child") => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (unsigned j = 0; j < ITERATIONS; j++) {
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
# lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0;
# lfs_file_close(&lfs, &file) => 0;
# }
#
# lfs_dir_t dir;
# struct lfs_info info;
# lfs_dir_open(&lfs, &dir, "child") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "test%03d_loooooooooooooooooong_name", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# strcmp(info.name, path) => 0;
# info.size => 0;
#
# sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
# lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0;
# lfs_file_write(&lfs, &file, "hi", 2) => 2;
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
#
# lfs_dir_rewind(&lfs, &dir) => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "test%03d_loooooooooooooooooong_name", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# strcmp(info.name, path) => 0;
# info.size => 2;
#
# sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
# lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0;
# lfs_file_write(&lfs, &file, "hi", 2) => 2;
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
#
# lfs_dir_rewind(&lfs, &dir) => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "test%03d_loooooooooooooooooong_name", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# strcmp(info.name, path) => 0;
# info.size => 2;
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
#
# for (unsigned i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
# lfs_remove(&lfs, path) => 0;
# }
# }
# lfs_unmount(&lfs) => 0;
#'''
#
## reentrant testing for relocations, this is the same as the
## orphan testing, except here we also set block_cycles so that
## almost every tree operation needs a relocation
#[cases.test_relocations_reentrant]
#reentrant = true
## TODO fix this case, caused by non-DAG trees
## NOTE the second condition is required
#if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
#defines = [
# {FILES=6, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1},
# {FILES=26, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1},
# {FILES=3, DEPTH=3, CYCLES=20, BLOCK_CYCLES=1},
#]
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
#
# uint32_t prng = 1;
# const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
# for (unsigned i = 0; i < CYCLES; i++) {
# // create random path
# char full_path[256];
# for (unsigned d = 0; d < DEPTH; d++) {
# sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
# }
#
# // if it does not exist, we create it, else we destroy
# struct lfs_info info;
# int res = lfs_stat(&lfs, full_path, &info);
# if (res == LFS_ERR_NOENT) {
# // create each directory in turn, ignore if dir already exists
# for (unsigned d = 0; d < DEPTH; d++) {
# char path[1024];
# strcpy(path, full_path);
# path[2*d+2] = '\0';
# err = lfs_mkdir(&lfs, path);
# assert(!err || err == LFS_ERR_EXIST);
# }
#
# for (unsigned d = 0; d < DEPTH; d++) {
# char path[1024];
# strcpy(path, full_path);
# path[2*d+2] = '\0';
# lfs_stat(&lfs, path, &info) => 0;
# assert(strcmp(info.name, &path[2*d+1]) == 0);
# assert(info.type == LFS_TYPE_DIR);
# }
# } else {
# // is valid dir?
# assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# // try to delete path in reverse order, ignore if dir is not empty
# for (unsigned d = DEPTH-1; d+1 > 0; d--) {
# char path[1024];
# strcpy(path, full_path);
# path[2*d+2] = '\0';
# err = lfs_remove(&lfs, path);
# assert(!err || err == LFS_ERR_NOTEMPTY);
# }
#
# lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
# }
# }
# lfs_unmount(&lfs) => 0;
#'''
#
## reentrant testing for relocations, but now with random renames!
#[cases.test_relocations_reentrant_renames]
#reentrant = true
## TODO fix this case, caused by non-DAG trees
## NOTE the second condition is required
#if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
#defines = [
# {FILES=6, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1},
# {FILES=26, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1},
# {FILES=3, DEPTH=3, CYCLES=20, BLOCK_CYCLES=1},
#]
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
#
# uint32_t prng = 1;
# const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
# for (unsigned i = 0; i < CYCLES; i++) {
# // create random path
# char full_path[256];
# for (unsigned d = 0; d < DEPTH; d++) {
# sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
# }
#
# // if it does not exist, we create it, else we destroy
# struct lfs_info info;
# int res = lfs_stat(&lfs, full_path, &info);
# assert(!res || res == LFS_ERR_NOENT);
# if (res == LFS_ERR_NOENT) {
# // create each directory in turn, ignore if dir already exists
# for (unsigned d = 0; d < DEPTH; d++) {
# char path[1024];
# strcpy(path, full_path);
# path[2*d+2] = '\0';
# err = lfs_mkdir(&lfs, path);
# assert(!err || err == LFS_ERR_EXIST);
# }
#
# for (unsigned d = 0; d < DEPTH; d++) {
# char path[1024];
# strcpy(path, full_path);
# path[2*d+2] = '\0';
# lfs_stat(&lfs, path, &info) => 0;
# assert(strcmp(info.name, &path[2*d+1]) == 0);
# assert(info.type == LFS_TYPE_DIR);
# }
# } else {
# assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# // create new random path
# char new_path[256];
# for (unsigned d = 0; d < DEPTH; d++) {
# sprintf(&new_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
# }
#
# // if new path does not exist, rename, otherwise destroy
# res = lfs_stat(&lfs, new_path, &info);
# assert(!res || res == LFS_ERR_NOENT);
# if (res == LFS_ERR_NOENT) {
# // stop once some dir is renamed
# for (unsigned d = 0; d < DEPTH; d++) {
# char path[1024];
# strcpy(&path[2*d], &full_path[2*d]);
# path[2*d+2] = '\0';
# strcpy(&path[128+2*d], &new_path[2*d]);
# path[128+2*d+2] = '\0';
# err = lfs_rename(&lfs, path, path+128);
# assert(!err || err == LFS_ERR_NOTEMPTY);
# if (!err) {
# strcpy(path, path+128);
# }
# }
#
# for (unsigned d = 0; d < DEPTH; d++) {
# char path[1024];
# strcpy(path, new_path);
# path[2*d+2] = '\0';
# lfs_stat(&lfs, path, &info) => 0;
# assert(strcmp(info.name, &path[2*d+1]) == 0);
# assert(info.type == LFS_TYPE_DIR);
# }
#
# lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
# } else {
# // try to delete path in reverse order,
# // ignore if dir is not empty
# for (unsigned d = DEPTH-1; d+1 > 0; d--) {
# char path[1024];
# strcpy(path, full_path);
# path[2*d+2] = '\0';
# err = lfs_remove(&lfs, path);
# assert(!err || err == LFS_ERR_NOTEMPTY);
# }
#
# lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
# }
# }
# }
# lfs_unmount(&lfs) => 0;
#'''