Files
littlefs/tests/test_alloc.toml
T
Christopher Haster 726cccfe76 bmap: Tweaked bmapcache algo to piggyback on mdir commits
There's really no reason to immediately commit the bmap to disk, at
least no until the first mdir commit, when we need to at least discard
the previous bmap state.

We already do all the gstate handling in lfs3_mdir_commit anyways, and
piggybacking on mdir commit lets us get rid of the annoying extra mdir
param in lfs3_alloc_ckpoint.

This does mean a slightly higher risk of needing to re-rebuild the bmap
after a powerloss, but in theory only if the user does something weird
like writing to a file and never calling sync. Most on-disk operations
terminate in an mdir commit as that's how any state change becomes
atomically visibile in littlefs.

Saves a nice bit of stack:

                code          stack          ctx
  before:      36920           2368          684
  after:       36920 (+0.0%)   2368 (+0.0%)  684 (+0.0%)

                code          stack          ctx
  bmap before: 38552           2472          812
  bmap after:  38464 (-0.2%)   2400 (-2.9%)  812 (+0.0%)
2025-10-01 17:56:16 -05:00

812 lines
24 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
#
after = ['test_mtree', 'test_bmap', 'test_dirs', 'test_files']
# TODO bmap workaround?
ifndef = 'LFS3_BMAP'
# test that we can alloc
[cases.test_alloc_alloc]
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'2',
]
defines.ERASE = [false, true]
in = 'lfs3.c'
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// start allocating
lfs3_alloc_ckpoint(&lfs3);
lfs3_size_t alloced = 0;
while (true) {
lfs3_sblock_t block = lfs3_alloc(&lfs3, ERASE);
assert(block >= 0 || block == LFS3_ERR_NOSPC);
if (block == LFS3_ERR_NOSPC) {
break;
}
alloced += 1;
// our allocator should stop at some point...
assert(alloced < 2*COUNT);
}
// excluding our mroot, we should have allocated exactly
// block_count-2 blocks
printf("alloced %d/%d blocks\n", alloced, (lfs3_block_t)COUNT);
assert(alloced == COUNT-2);
lfs3_unmount(&lfs3) => 0;
'''
# test that we can realloc after an ack
[cases.test_alloc_reuse]
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'2',
]
defines.ERASE = [false, true]
in = 'lfs3.c'
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// start allocating
lfs3_alloc_ckpoint(&lfs3);
lfs3_size_t alloced = 0;
while (true) {
lfs3_sblock_t block = lfs3_alloc(&lfs3, ERASE);
assert(block >= 0 || block == LFS3_ERR_NOSPC);
if (block == LFS3_ERR_NOSPC) {
break;
}
alloced += 1;
// our allocator should stop at some point...
assert(alloced < 2*COUNT);
}
// excluding our mroot, we should have allocated exactly
// block_count-2 blocks
printf("alloced %d/%d blocks\n", alloced, (lfs3_block_t)COUNT);
assert(alloced == COUNT-2);
// ack again, effectively releasing all the previously alloced blocks
lfs3_alloc_ckpoint(&lfs3);
alloced = 0;
while (true) {
lfs3_sblock_t block = lfs3_alloc(&lfs3, ERASE);
assert(block >= 0 || block == LFS3_ERR_NOSPC);
if (block == LFS3_ERR_NOSPC) {
break;
}
alloced += 1;
// our allocator should stop at some point...
assert(alloced < 2*COUNT);
}
// excluding our mroot, we should have allocated exactly
// block_count-2 blocks
printf("alloced %d/%d blocks\n", alloced, (lfs3_block_t)COUNT);
assert(alloced == COUNT-2);
lfs3_unmount(&lfs3) => 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.CKMETA = [false, true]
defines.REMOUNT = [false, true]
in = 'lfs3.c'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// create this many directories
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
lfs3_mkdir(&lfs3, name) => 0;
}
// check that our mkdir worked
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
}
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
struct lfs3_info info;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
}
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// remount?
if (REMOUNT) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, 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);
lfs3_trv_t trv;
lfs3_trv_init(&trv,
LFS3_T_RDONLY
| ((CKMETA) ? LFS3_T_CKMETA : 0));
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_bptr_t bptr;
tag = lfs3_mtree_traverse(&lfs3, &trv,
&bptr);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
break;
}
if (tag == LFS3_TAG_MDIR) {
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
tag,
mdir->r.blocks[0],
mdir->r.blocks[1]);
// keep track of seen blocks
seen[mdir->r.blocks[1] / 8] |= 1 << (mdir->r.blocks[1] % 8);
seen[mdir->r.blocks[0] / 8] |= 1 << (mdir->r.blocks[0] % 8);
} else if (tag == LFS3_TAG_BRANCH) {
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
printf("traversal: 0x%x btree 0x%x.%x\n",
tag,
rbyd->blocks[0], rbyd->trunk);
// keep track of seen blocks
seen[rbyd->blocks[0] / 8] |= 1 << (rbyd->blocks[0] % 8);
} else {
// this shouldn't happen
printf("traversal: 0x%x\n", tag);
assert(false);
}
}
// then clobber every other block
uint8_t clobber_buf[BLOCK_SIZE];
memset(clobber_buf, 0xcc, BLOCK_SIZE);
for (lfs3_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) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
}
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
}
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
}
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_alloc_clobber_files]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.SIZE = [
'0',
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
defines.CKMETA = [false, true]
defines.REMOUNT = [false, true]
in = 'lfs3.c'
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// create this many files
uint32_t prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "file%03x", i);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
// check that our writes worked
prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%03x", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
// remount?
if (REMOUNT) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, 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);
lfs3_trv_t trv;
lfs3_trv_init(&trv,
LFS3_T_RDONLY
| ((CKMETA) ? LFS3_T_CKMETA : 0));
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_bptr_t bptr;
tag = lfs3_mtree_traverse(&lfs3, &trv,
&bptr);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
break;
}
if (tag == LFS3_TAG_MDIR) {
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
tag,
mdir->r.blocks[0],
mdir->r.blocks[1]);
// keep track of seen blocks
seen[mdir->r.blocks[1] / 8] |= 1 << (mdir->r.blocks[1] % 8);
seen[mdir->r.blocks[0] / 8] |= 1 << (mdir->r.blocks[0] % 8);
} else if (tag == LFS3_TAG_BRANCH) {
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
printf("traversal: 0x%x btree 0x%x.%x\n",
tag,
rbyd->blocks[0], rbyd->trunk);
// keep track of seen blocks
seen[rbyd->blocks[0] / 8] |= 1 << (rbyd->blocks[0] % 8);
} else if (tag == LFS3_TAG_BLOCK) {
printf("traversal: 0x%x block 0x%x\n",
tag,
bptr.d.u.disk.block);
// keep track of seen blocks
seen[bptr.d.u.disk.block / 8]
|= 1 << (bptr.d.u.disk.block % 8);
} else {
// this shouldn't happen
printf("traversal: 0x%x\n", tag);
assert(false);
}
}
// then clobber every other block
uint8_t clobber_buf[BLOCK_SIZE];
memset(clobber_buf, 0xcc, BLOCK_SIZE);
for (lfs3_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) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%03x", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
lfs3_unmount(&lfs3) => 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_CACHE_SIZE/2',
'2*FILE_CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
defines.CKMETA = [false, true]
in = 'lfs3.c'
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// create this many files
lfs3_file_t files[N];
uint32_t prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "file%03x", i);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_open(&lfs3, &files[i], name,
LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &files[i], wbuf, SIZE) => SIZE;
}
// check that our writes worked
prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t rbuf[SIZE];
lfs3_file_rewind(&lfs3, &files[i]) => 0;
lfs3_file_read(&lfs3, &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);
lfs3_trv_t trv;
lfs3_trv_init(&trv,
LFS3_T_RDONLY
| ((CKMETA) ? LFS3_T_CKMETA : 0));
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_bptr_t bptr;
tag = lfs3_mtree_traverse(&lfs3, &trv,
&bptr);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
break;
}
if (tag == LFS3_TAG_MDIR) {
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
tag,
mdir->r.blocks[0],
mdir->r.blocks[1]);
// keep track of seen blocks
seen[mdir->r.blocks[1] / 8]
|= 1 << (mdir->r.blocks[1] % 8);
seen[mdir->r.blocks[0] / 8]
|= 1 << (mdir->r.blocks[0] % 8);
} else if (tag == LFS3_TAG_BRANCH) {
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
printf("traversal: 0x%x btree 0x%x.%x\n",
tag,
rbyd->blocks[0], rbyd->trunk);
// keep track of seen blocks
seen[rbyd->blocks[0] / 8]
|= 1 << (rbyd->blocks[0] % 8);
} else if (tag == LFS3_TAG_BLOCK) {
printf("traversal: 0x%x block 0x%x\n",
tag,
bptr.d.u.disk.block);
// keep track of seen blocks
seen[bptr.d.u.disk.block / 8]
|= 1 << (bptr.d.u.disk.block % 8);
} else {
// this shouldn't happen
printf("traversal: 0x%x\n", tag);
assert(false);
}
}
// then clobber every other block
uint8_t clobber_buf[BLOCK_SIZE];
memset(clobber_buf, 0xcc, BLOCK_SIZE);
for (lfs3_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 (lfs3_size_t i = 0; i < N; i++) {
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t rbuf[SIZE];
lfs3_file_rewind(&lfs3, &files[i]) => 0;
lfs3_file_read(&lfs3, &files[i], rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// and everything is fine after saving the files
for (lfs3_size_t i = 0; i < N; i++) {
lfs3_file_close(&lfs3, &files[i]) => 0;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%03x", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
lfs3_unmount(&lfs3) => 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]
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'2',
]
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// create directories until we run out of space
lfs3_size_t n = 0;
for (;; n++) {
char name[256];
sprintf(name, "dir%08d", n);
int err = lfs3_mkdir(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
break;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// check that our mkdir worked until we ran out of space
for (lfs3_size_t i = 0; i < n; i++) {
char name[256];
sprintf(name, "dir%08d", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
}
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
struct lfs3_info info;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
for (lfs3_size_t i = 0; i < n; i++) {
char name[256];
sprintf(name, "dir%08d", i);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
}
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
}
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_alloc_nospc_files]
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'2',
]
defines.SIZE = [
'0',
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// create files until we run out of space
uint32_t prng = 42;
lfs3_size_t n = 0;
for (;; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_t file;
int err = lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
break;
}
lfs3_ssize_t size = lfs3_file_write(&lfs3, &file, wbuf, SIZE);
assert(size == SIZE || size == LFS3_ERR_NOSPC);
if (size == LFS3_ERR_NOSPC) {
lfs3_file_close(&lfs3, &file) => 0;
break;
}
err = lfs3_file_close(&lfs3, &file);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
break;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// check that our file writes worked until we ran out of space
prng = 42;
for (lfs3_size_t i = 0; i < n; i++) {
// check with stat
char name[256];
sprintf(name, "file%08d", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
lfs3_unmount(&lfs3) => 0;
'''