Files
littlefs/tests/test_alloc.toml
T
Christopher Haster 670b9fbf99 t: Implemented rudimentary lfsr_traversal_t and related functions
This adds the lfsr_traversal_t object, which encapsulates a traversal
over all blocks in the filesystem.

This replaces the earlier lfs_fs_traverse function, but is sort of
"inside-out" in that instead of taking a callback, an lfsr_traversal_t
object can be read from to return lfs_tinfo structs that describe the
blocks in our system:

  lfsr_traversal_open(&lfs, &t) => 0;
  lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
  tinfo.btype => LFS_BTYPE_MDIR;
  tinfo.block => 0x0;
  lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
  tinfo.btype => LFS_BTYPE_MDIR;
  tinfo.block => 0x1;
  lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
  tinfo.btype => LFS_BTYPE_DATA;
  tinfo.block => 0x42;
  lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT;
  lfsr_traversal_close(&lfs, &t) => 0;

This is more flexible, allowing for aborted traversals, yielding,
rewinding, etc, but also more complicated to implement, since it
requires all traversal state to be stored explicitly.

Fortunately, since we needed to reimplement filesystem traversals
anyways, I was able to build this into the new system from the start
using a small state machine to drive the traversal internally. So all
that was really needed was a bit of window dressing, adding
LFS_TYPE_TRAVERSAL to track open traversals, logic to handle
invalidating traversals on file close, mutation, etc...

Which, uh, that last one is not implemented yet. Interactions with other
filesystem operations gets messy, so I figured I'd go ahead and commit
what is currently working.

Ugh, and tests. The biggest downside of adding lfsr_traversal_t is how
many more corner-cases it adds to the system...

lfsr_traversal_t is going to be a work-in-progress for a bit...

---

lfsr_traversal_t also adds a really interesting path towards more access
to advanced low-level operations, such as checking metadata/data
checksums, incrementally progressing the garbage collector, even
repairing bad metadata/data blocks eventually.

Currently implemented is LFS_T_CKMETADATA and LFS_T_CKDATA to check
metadata and data checksums respectively. This is the first feature that
actually allows you to validate data checksums.

Code changes so far:

           code          stack
  before: 33886           2560
  after:  34226 (+1.0%)   2560 (+0.0%)
2024-06-20 13:09:24 -05:00

1480 lines
46 KiB
TOML

# Tests covering properties of the block allocator
# The ordering of these tests vs higher-level tests (files/dirs/etc) gets
# a bit weird because there is an inherent cyclic dependency
#
# It's counter-intuitive, but we run the alloc tests _after_ file/dir tests,
# since you can usually ignore allocator issues temporarily by making the test
# device really big (-DDISK_SIZE=16777216, etc)
#
after = ['test_mtree', 'test_dirs', 'test_files']
# TODO test all of these with weird block sizes? would be nice to make this
# easy via the test_runner, either by handling it there or letting a single
# config limit the block count by a couple blocks
# test that we can alloc
[cases.test_alloc_alloc]
in = 'lfs.c'
defines.ERASE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// start allocating
lfs_alloc_ckpoint(&lfs);
lfs_size_t alloced = 0;
while (true) {
lfs_block_t block;
int err = lfs_alloc(&lfs, &block, ERASE);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
break;
}
alloced += 1;
// our allocator should stop at some point...
assert(alloced < 2*BLOCK_COUNT);
}
// excluding our mroot, we should have allocated exactly
// block_count-2 blocks
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
assert(alloced == BLOCK_COUNT-2);
lfsr_unmount(&lfs) => 0;
'''
# test that we can realloc after an ack
[cases.test_alloc_reuse]
in = 'lfs.c'
defines.ERASE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// start allocating
lfs_alloc_ckpoint(&lfs);
lfs_size_t alloced = 0;
while (true) {
lfs_block_t block;
int err = lfs_alloc(&lfs, &block, ERASE);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
break;
}
alloced += 1;
// our allocator should stop at some point...
assert(alloced < 2*BLOCK_COUNT);
}
// excluding our mroot, we should have allocated exactly
// block_count-2 blocks
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
assert(alloced == BLOCK_COUNT-2);
// ack again, effectively releasing all the previously alloced blocks
lfs_alloc_ckpoint(&lfs);
alloced = 0;
while (true) {
lfs_block_t block;
int err = lfs_alloc(&lfs, &block, ERASE);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
break;
}
alloced += 1;
// our allocator should stop at some point...
assert(alloced < 2*BLOCK_COUNT);
}
// excluding our mroot, we should have allocated exactly
// block_count-2 blocks
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
assert(alloced == BLOCK_COUNT-2);
lfsr_unmount(&lfs) => 0;
'''
# clobber tests test that our traversal algorithm works
[cases.test_alloc_clobber_dirs]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.CKMETADATA = [false, true]
defines.REMOUNT = [false, true]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create this many directories
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
lfsr_mkdir(&lfs, name) => 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;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// first traverse the tree to find all blocks in use
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(
(CKMETADATA) ? LFS_T_CKMETADATA : 0);
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfsr_mtinfo_t mtinfo;
int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (mtinfo.tag == LFSR_TAG_MDIR) {
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
mtinfo.tag,
mtinfo.u.mdir.rbyd.blocks[0],
mtinfo.u.mdir.rbyd.blocks[1]);
// keep track of seen blocks
seen[mtinfo.u.mdir.rbyd.blocks[1] / 8]
|= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8);
seen[mtinfo.u.mdir.rbyd.blocks[0] / 8]
|= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8);
} else if (mtinfo.tag == LFSR_TAG_BRANCH) {
printf("traversal: 0x%x btree 0x%x.%x\n",
mtinfo.tag,
mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk);
// keep track of seen blocks
seen[mtinfo.u.rbyd.blocks[0] / 8]
|= 1 << (mtinfo.u.rbyd.blocks[0] % 8);
} else {
// this shouldn't happen
printf("traversal: 0x%x\n", mtinfo.tag);
assert(false);
}
}
// then clobber every other block
uint8_t clobber_buf[BLOCK_SIZE];
memset(clobber_buf, 0xcc, BLOCK_SIZE);
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
if (!(seen[block / 8] & (1 << (block % 8)))) {
CFG->erase(CFG, block) => 0;
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
}
}
free(seen);
// then check that we can read our directories after clobbering
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
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_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);
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;
}
lfsr_unmount(&lfs) => 0;
'''
[cases.test_alloc_clobber_files]
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',
'8*BLOCK_SIZE',
]
defines.CKMETADATA = [false, true]
defines.REMOUNT = [false, true]
in = 'lfs.c'
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create this many files
uint32_t prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "file%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;
}
// check that our writes worked
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%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;
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// first traverse the tree to find all blocks in use
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(
((CKMETADATA) ? LFS_T_CKMETADATA : 0));
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfsr_mtinfo_t mtinfo;
int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (mtinfo.tag == LFSR_TAG_MDIR) {
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
mtinfo.tag,
mtinfo.u.mdir.rbyd.blocks[0],
mtinfo.u.mdir.rbyd.blocks[1]);
// keep track of seen blocks
seen[mtinfo.u.mdir.rbyd.blocks[1] / 8]
|= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8);
seen[mtinfo.u.mdir.rbyd.blocks[0] / 8]
|= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8);
} else if (mtinfo.tag == LFSR_TAG_BRANCH) {
printf("traversal: 0x%x btree 0x%x.%x\n",
mtinfo.tag,
mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk);
// keep track of seen blocks
seen[mtinfo.u.rbyd.blocks[0] / 8]
|= 1 << (mtinfo.u.rbyd.blocks[0] % 8);
} else if (mtinfo.tag == LFSR_TAG_BLOCK) {
printf("traversal: 0x%x block 0x%x\n",
mtinfo.tag,
mtinfo.u.bptr.data.u.disk.block);
// keep track of seen blocks
seen[mtinfo.u.bptr.data.u.disk.block / 8]
|= 1 << (mtinfo.u.bptr.data.u.disk.block % 8);
} else {
// this shouldn't happen
printf("traversal: 0x%x\n", mtinfo.tag);
assert(false);
}
}
// then clobber every other block
uint8_t clobber_buf[BLOCK_SIZE];
memset(clobber_buf, 0xcc, BLOCK_SIZE);
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
if (!(seen[block / 8] & (1 << (block % 8)))) {
CFG->erase(CFG, block) => 0;
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
}
}
free(seen);
// then check that reading our files still works after clobbering
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%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;
'''
# open files need to be tracked internally to make sure this doesn't break
[cases.test_alloc_clobber_open_files]
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',
'8*BLOCK_SIZE',
]
defines.CKMETADATA = [false, true]
in = 'lfs.c'
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create this many files
lfsr_file_t files[N];
uint32_t prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "file%03x", i);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_open(&lfs, &files[i], name,
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE;
}
// check that our writes worked
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// 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);
}
uint8_t rbuf[SIZE];
lfsr_file_rewind(&lfs, &files[i]) => 0;
lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// first traverse the tree to find all blocks in use
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(
((CKMETADATA) ? LFS_T_CKMETADATA : 0));
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfsr_mtinfo_t mtinfo;
int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (mtinfo.tag == LFSR_TAG_MDIR) {
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
mtinfo.tag,
mtinfo.u.mdir.rbyd.blocks[0],
mtinfo.u.mdir.rbyd.blocks[1]);
// keep track of seen blocks
seen[mtinfo.u.mdir.rbyd.blocks[1] / 8]
|= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8);
seen[mtinfo.u.mdir.rbyd.blocks[0] / 8]
|= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8);
} else if (mtinfo.tag == LFSR_TAG_BRANCH) {
printf("traversal: 0x%x btree 0x%x.%x\n",
mtinfo.tag,
mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk);
// keep track of seen blocks
seen[mtinfo.u.rbyd.blocks[0] / 8]
|= 1 << (mtinfo.u.rbyd.blocks[0] % 8);
} else if (mtinfo.tag == LFSR_TAG_BLOCK) {
printf("traversal: 0x%x block 0x%x\n",
mtinfo.tag,
mtinfo.u.bptr.data.u.disk.block);
// keep track of seen blocks
seen[mtinfo.u.bptr.data.u.disk.block / 8]
|= 1 << (mtinfo.u.bptr.data.u.disk.block % 8);
} else {
// this shouldn't happen
printf("traversal: 0x%x\n", mtinfo.tag);
assert(false);
}
}
// then clobber every other block
uint8_t clobber_buf[BLOCK_SIZE];
memset(clobber_buf, 0xcc, BLOCK_SIZE);
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
if (!(seen[block / 8] & (1 << (block % 8)))) {
CFG->erase(CFG, block) => 0;
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
}
}
free(seen);
// then check that reading our files still works after clobbering
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// 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);
}
uint8_t rbuf[SIZE];
lfsr_file_rewind(&lfs, &files[i]) => 0;
lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// and everything is fine after saving the files
for (lfs_size_t i = 0; i < N; i++) {
lfsr_file_close(&lfs, &files[i]) => 0;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%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;
'''
# TODO more nospc tests (opened files? other?)
# nospc tests mostly test that things still work when block allocation
# wraparound occurs
[cases.test_alloc_nospc_dirs]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create directories until we run out of space
lfs_size_t n = 0;
for (;; n++) {
char name[256];
sprintf(name, "dir%08d", n);
int err = lfsr_mkdir(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
break;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our mkdir worked until we ran out of space
for (lfs_size_t i = 0; i < n; i++) {
char name[256];
sprintf(name, "dir%08d", 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%08d", 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;
}
lfsr_unmount(&lfs) => 0;
'''
[cases.test_alloc_nospc_files]
defines.SIZE = [
'0',
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create files until we run out of space
uint32_t prng = 42;
lfs_size_t n = 0;
for (;; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
int err = lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
break;
}
lfs_ssize_t size = lfsr_file_write(&lfs, &file, wbuf, SIZE);
assert(size == SIZE || size == LFS_ERR_NOSPC);
if (size == LFS_ERR_NOSPC) {
lfsr_file_close(&lfs, &file) => 0;
break;
}
err = lfsr_file_close(&lfs, &file);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
break;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, CFG) => 0;
}
// check that our file writes worked until we ran out of space
prng = 42;
for (lfs_size_t i = 0; i < n; i++) {
// check with stat
char name[256];
sprintf(name, "file%08d", 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;
'''
## allocator tests
## note for these to work there are a number constraints on the device geometry
#if = 'BLOCK_CYCLES == -1'
#
## parallel allocation test
#[cases.test_alloc_parallel]
#defines.FILES = 3
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
#code = '''
# const char *names[] = {"bacon", "eggs", "pancakes"};
# lfs_file_t files[FILES];
#
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "breakfast") => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int n = 0; n < FILES; n++) {
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_file_open(&lfs, &files[n], path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
# }
# for (int n = 0; n < FILES; n++) {
# size_t size = strlen(names[n]);
# for (lfs_size_t i = 0; i < SIZE; i += size) {
# lfs_file_write(&lfs, &files[n], names[n], size) => size;
# }
# }
# for (int n = 0; n < FILES; n++) {
# lfs_file_close(&lfs, &files[n]) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int n = 0; n < FILES; n++) {
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
# size_t size = strlen(names[n]);
# for (lfs_size_t i = 0; i < SIZE; i += size) {
# uint8_t buffer[1024];
# lfs_file_read(&lfs, &file, buffer, size) => size;
# assert(memcmp(buffer, names[n], size) == 0);
# }
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''
#
## serial allocation test
#[cases.test_alloc_serial]
#defines.FILES = 3
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
#code = '''
# const char *names[] = {"bacon", "eggs", "pancakes"};
#
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "breakfast") => 0;
# lfs_unmount(&lfs) => 0;
#
# for (int n = 0; n < FILES; n++) {
# lfs_mount(&lfs, cfg) => 0;
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
# size_t size = strlen(names[n]);
# uint8_t buffer[1024];
# memcpy(buffer, names[n], size);
# for (int i = 0; i < SIZE; i += size) {
# 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 n = 0; n < FILES; n++) {
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
# size_t size = strlen(names[n]);
# for (int i = 0; i < SIZE; i += size) {
# uint8_t buffer[1024];
# lfs_file_read(&lfs, &file, buffer, size) => size;
# assert(memcmp(buffer, names[n], size) == 0);
# }
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''
#
## parallel allocation reuse test
#[cases.test_alloc_parallel_reuse]
#defines.FILES = 3
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
#defines.CYCLES = [1, 10]
#code = '''
# const char *names[] = {"bacon", "eggs", "pancakes"};
# lfs_file_t files[FILES];
#
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# for (int c = 0; c < CYCLES; c++) {
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "breakfast") => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int n = 0; n < FILES; n++) {
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_file_open(&lfs, &files[n], path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
# }
# for (int n = 0; n < FILES; n++) {
# size_t size = strlen(names[n]);
# for (int i = 0; i < SIZE; i += size) {
# lfs_file_write(&lfs, &files[n], names[n], size) => size;
# }
# }
# for (int n = 0; n < FILES; n++) {
# lfs_file_close(&lfs, &files[n]) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int n = 0; n < FILES; n++) {
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
# size_t size = strlen(names[n]);
# for (int i = 0; i < SIZE; i += size) {
# uint8_t buffer[1024];
# lfs_file_read(&lfs, &file, buffer, size) => size;
# assert(memcmp(buffer, names[n], size) == 0);
# }
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int n = 0; n < FILES; n++) {
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_remove(&lfs, path) => 0;
# }
# lfs_remove(&lfs, "breakfast") => 0;
# lfs_unmount(&lfs) => 0;
# }
#'''
#
## serial allocation reuse test
#[cases.test_alloc_serial_reuse]
#defines.FILES = 3
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
#defines.CYCLES = [1, 10]
#code = '''
# const char *names[] = {"bacon", "eggs", "pancakes"};
#
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# for (int c = 0; c < CYCLES; c++) {
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "breakfast") => 0;
# lfs_unmount(&lfs) => 0;
#
# for (int n = 0; n < FILES; n++) {
# lfs_mount(&lfs, cfg) => 0;
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
# size_t size = strlen(names[n]);
# uint8_t buffer[1024];
# memcpy(buffer, names[n], size);
# for (int i = 0; i < SIZE; i += size) {
# 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 n = 0; n < FILES; n++) {
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
# size_t size = strlen(names[n]);
# for (int i = 0; i < SIZE; i += size) {
# uint8_t buffer[1024];
# lfs_file_read(&lfs, &file, buffer, size) => size;
# assert(memcmp(buffer, names[n], size) == 0);
# }
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int n = 0; n < FILES; n++) {
# char path[1024];
# sprintf(path, "breakfast/%s", names[n]);
# lfs_remove(&lfs, path) => 0;
# }
# lfs_remove(&lfs, "breakfast") => 0;
# lfs_unmount(&lfs) => 0;
# }
#'''
#
## exhaustion test
#[cases.test_alloc_exhaustion]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
# size_t size = strlen("exhaustion");
# uint8_t buffer[1024];
# memcpy(buffer, "exhaustion", size);
# lfs_file_write(&lfs, &file, buffer, size) => size;
# lfs_file_sync(&lfs, &file) => 0;
#
# size = strlen("blahblahblahblah");
# memcpy(buffer, "blahblahblahblah", size);
# lfs_ssize_t res;
# while (true) {
# res = lfs_file_write(&lfs, &file, buffer, size);
# if (res < 0) {
# break;
# }
#
# res => size;
# }
# res => LFS_ERR_NOSPC;
#
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY);
# size = strlen("exhaustion");
# lfs_file_size(&lfs, &file) => size;
# lfs_file_read(&lfs, &file, buffer, size) => size;
# memcmp(buffer, "exhaustion", size) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
## exhaustion wraparound test
#[cases.test_alloc_exhaustion_wraparound]
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-4)) / 3)'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "padding", LFS_O_WRONLY | LFS_O_CREAT);
# size_t size = strlen("buffering");
# uint8_t buffer[1024];
# memcpy(buffer, "buffering", size);
# for (int i = 0; i < SIZE; i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_remove(&lfs, "padding") => 0;
#
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
# size = strlen("exhaustion");
# memcpy(buffer, "exhaustion", size);
# lfs_file_write(&lfs, &file, buffer, size) => size;
# lfs_file_sync(&lfs, &file) => 0;
#
# size = strlen("blahblahblahblah");
# memcpy(buffer, "blahblahblahblah", size);
# lfs_ssize_t res;
# while (true) {
# res = lfs_file_write(&lfs, &file, buffer, size);
# if (res < 0) {
# break;
# }
#
# res => size;
# }
# res => LFS_ERR_NOSPC;
#
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY);
# size = strlen("exhaustion");
# lfs_file_size(&lfs, &file) => size;
# lfs_file_read(&lfs, &file, buffer, size) => size;
# memcmp(buffer, "exhaustion", size) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_remove(&lfs, "exhaustion") => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
## dir exhaustion test
#[cases.test_alloc_dir_exhaustion]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# // find out max file size
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
# size_t size = strlen("blahblahblahblah");
# uint8_t buffer[1024];
# memcpy(buffer, "blahblahblahblah", size);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
# int count = 0;
# int err;
# while (true) {
# err = lfs_file_write(&lfs, &file, buffer, size);
# if (err < 0) {
# break;
# }
#
# count += 1;
# }
# err => LFS_ERR_NOSPC;
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_remove(&lfs, "exhaustion") => 0;
# lfs_remove(&lfs, "exhaustiondir") => 0;
#
# // see if dir fits with max file size
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
# for (int i = 0; i < count; i++) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
# lfs_remove(&lfs, "exhaustiondir") => 0;
# lfs_remove(&lfs, "exhaustion") => 0;
#
# // see if dir fits with > max file size
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
# for (int i = 0; i < count+1; i++) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
#
# lfs_remove(&lfs, "exhaustion") => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
## what if we have a bad block during an allocation scan?
#[cases.test_alloc_bad_blocks]
#in = "lfs.c"
#defines.ERASE_CYCLES = 0xffffffff
#defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_READERROR'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# // first fill to exhaustion to find available space
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# uint8_t buffer[1024];
# strcpy((char*)buffer, "waka");
# size_t size = strlen("waka");
# lfs_size_t filesize = 0;
# while (true) {
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
# if (res == LFS_ERR_NOSPC) {
# break;
# }
# filesize += size;
# }
# lfs_file_close(&lfs, &file) => 0;
# // now fill all but a couple of blocks of the filesystem with data
# filesize -= 3*BLOCK_SIZE;
# lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# strcpy((char*)buffer, "waka");
# size = strlen("waka");
# for (lfs_size_t i = 0; i < filesize/size; i++) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
# // also save head of file so we can error during lookahead scan
# lfs_block_t fileblock = file.ctz.head;
# lfs_unmount(&lfs) => 0;
#
# // remount to force an alloc scan
# lfs_mount(&lfs, cfg) => 0;
#
# // but mark the head of our file as a "bad block", this is force our
# // scan to bail early
# lfs_emubd_setwear(cfg, fileblock, 0xffffffff) => 0;
# lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# strcpy((char*)buffer, "chomp");
# size = strlen("chomp");
# while (true) {
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_CORRUPT);
# if (res == LFS_ERR_CORRUPT) {
# break;
# }
# }
# lfs_file_close(&lfs, &file) => 0;
#
# // now reverse the "bad block" and try to write the file again until we
# // run out of space
# lfs_emubd_setwear(cfg, fileblock, 0) => 0;
# lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# strcpy((char*)buffer, "chomp");
# size = strlen("chomp");
# while (true) {
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
# if (res == LFS_ERR_NOSPC) {
# break;
# }
# }
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_unmount(&lfs) => 0;
#
# // check that the disk isn't hurt
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0;
# strcpy((char*)buffer, "waka");
# size = strlen("waka");
# for (lfs_size_t i = 0; i < filesize/size; i++) {
# uint8_t rbuffer[4];
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
# assert(memcmp(rbuffer, buffer, size) == 0);
# }
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#
## Below, I don't like these tests. They're fragile and depend _heavily_
## on the geometry of the block device. But they are valuable. Eventually they
## should be removed and replaced with generalized tests.
#
## chained dir exhaustion test
#[cases.test_alloc_chained_dir_exhaustion]
#if = 'BLOCK_SIZE == 512'
#defines.BLOCK_COUNT = 1024
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# // find out max file size
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
# for (int i = 0; i < 10; i++) {
# char path[1024];
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
# lfs_mkdir(&lfs, path) => 0;
# }
# size_t size = strlen("blahblahblahblah");
# uint8_t buffer[1024];
# memcpy(buffer, "blahblahblahblah", size);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
# int count = 0;
# int err;
# while (true) {
# err = lfs_file_write(&lfs, &file, buffer, size);
# if (err < 0) {
# break;
# }
#
# count += 1;
# }
# err => LFS_ERR_NOSPC;
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_remove(&lfs, "exhaustion") => 0;
# lfs_remove(&lfs, "exhaustiondir") => 0;
# for (int i = 0; i < 10; i++) {
# char path[1024];
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
# lfs_remove(&lfs, path) => 0;
# }
#
# // see that chained dir fails
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
# for (int i = 0; i < count+1; i++) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_sync(&lfs, &file) => 0;
#
# for (int i = 0; i < 10; i++) {
# char path[1024];
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
# lfs_mkdir(&lfs, path) => 0;
# }
#
# lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
#
# // shorten file to try a second chained dir
# while (true) {
# err = lfs_mkdir(&lfs, "exhaustiondir");
# if (err != LFS_ERR_NOSPC) {
# break;
# }
#
# lfs_ssize_t filesize = lfs_file_size(&lfs, &file);
# filesize > 0 => true;
#
# lfs_file_truncate(&lfs, &file, filesize - size) => 0;
# lfs_file_sync(&lfs, &file) => 0;
# }
# err => 0;
#
# lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC;
#
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
## split dir test
#[cases.test_alloc_split_dir]
#if = 'BLOCK_SIZE == 512'
#defines.BLOCK_COUNT = 1024
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# // create one block hole for half a directory
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# for (lfs_size_t i = 0; i < cfg->block_size; i += 2) {
# uint8_t buffer[1024];
# memcpy(&buffer[i], "hi", 2);
# }
# uint8_t buffer[1024];
# lfs_file_write(&lfs, &file, buffer, cfg->block_size) => cfg->block_size;
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
# size_t size = strlen("blahblahblahblah");
# memcpy(buffer, "blahblahblahblah", size);
# for (lfs_size_t i = 0;
# i < (cfg->block_count-4)*(cfg->block_size-8);
# i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# // remount to force reset of lookahead
# lfs_unmount(&lfs) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# // open hole
# lfs_remove(&lfs, "bump") => 0;
#
# lfs_mkdir(&lfs, "splitdir") => 0;
# lfs_file_open(&lfs, &file, "splitdir/bump",
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
# for (lfs_size_t i = 0; i < cfg->block_size; i += 2) {
# memcpy(&buffer[i], "hi", 2);
# }
# lfs_file_write(&lfs, &file, buffer, 2*cfg->block_size) => LFS_ERR_NOSPC;
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_unmount(&lfs) => 0;
#'''
#
## outdated lookahead test
#[cases.test_alloc_outdated_lookahead]
#if = 'BLOCK_SIZE == 512'
#defines.BLOCK_COUNT = 1024
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# // fill completely with two files
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "exhaustion1",
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
# size_t size = strlen("blahblahblahblah");
# uint8_t buffer[1024];
# memcpy(buffer, "blahblahblahblah", size);
# for (lfs_size_t i = 0;
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
# i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_file_open(&lfs, &file, "exhaustion2",
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
# size = strlen("blahblahblahblah");
# memcpy(buffer, "blahblahblahblah", size);
# for (lfs_size_t i = 0;
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
# i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# // remount to force reset of lookahead
# lfs_unmount(&lfs) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# // rewrite one file
# lfs_file_open(&lfs, &file, "exhaustion1",
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
# lfs_file_sync(&lfs, &file) => 0;
# size = strlen("blahblahblahblah");
# memcpy(buffer, "blahblahblahblah", size);
# for (lfs_size_t i = 0;
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
# i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# // rewrite second file, this requires lookahead does not
# // use old population
# lfs_file_open(&lfs, &file, "exhaustion2",
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
# lfs_file_sync(&lfs, &file) => 0;
# size = strlen("blahblahblahblah");
# memcpy(buffer, "blahblahblahblah", size);
# for (lfs_size_t i = 0;
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
# i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_unmount(&lfs) => 0;
#'''
#
## outdated lookahead and split dir test
#[cases.test_alloc_outdated_lookahead_split_dir]
#if = 'BLOCK_SIZE == 512'
#defines.BLOCK_COUNT = 1024
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# // fill completely with two files
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "exhaustion1",
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
# size_t size = strlen("blahblahblahblah");
# uint8_t buffer[1024];
# memcpy(buffer, "blahblahblahblah", size);
# for (lfs_size_t i = 0;
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
# i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_file_open(&lfs, &file, "exhaustion2",
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
# size = strlen("blahblahblahblah");
# memcpy(buffer, "blahblahblahblah", size);
# for (lfs_size_t i = 0;
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
# i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# // remount to force reset of lookahead
# lfs_unmount(&lfs) => 0;
# lfs_mount(&lfs, cfg) => 0;
#
# // rewrite one file with a hole of one block
# lfs_file_open(&lfs, &file, "exhaustion1",
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
# lfs_file_sync(&lfs, &file) => 0;
# size = strlen("blahblahblahblah");
# memcpy(buffer, "blahblahblahblah", size);
# for (lfs_size_t i = 0;
# i < ((cfg->block_count-2)/2 - 1)*(cfg->block_size-8);
# i += size) {
# lfs_file_write(&lfs, &file, buffer, size) => size;
# }
# lfs_file_close(&lfs, &file) => 0;
#
# // try to allocate a directory, should fail!
# lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC;
#
# // file should not fail
# lfs_file_open(&lfs, &file, "notasplit",
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
# lfs_file_write(&lfs, &file, "hi", 2) => 2;
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_unmount(&lfs) => 0;
#'''