gc: Dropped lfsr_gc_setflags/setsteps

Now that you can provide gc_flags/gc_steps in lfs_config, I think it's a
bit more clear that _mutating_ the flags/steps is a niche feature, and
not worth implementing/testing.

It raises the question why not have a similar lfsr_setflags or
lfsr_file_setflags, and the answer there is it would be a pain-in-the-
ass to make sure all possible corner cases are covered.

It actually already was a pain-in-the-ass to test lfsr_gcsetflags/
setsteps... but just because we already did the work is not a good
reason for keeping complexity around.

---

Note that most of the use cases for lfsr_gc_setflags/setsteps can be
covered by either remounting the filesystem or through the
lfsr_traversal_t APIs directly.

The end result is a bit of code savings when incremental gc is enabled:

                   code          stack          ctx
  default before: 37796           2608          620
  default after:  37796 (+0.0%)   2608 (+0.0%)  620 (+0.0%)

  gc before:      37944           2608          768
  gc after        37896 (-0.1%)   2608 (+0.0%)  768 (+0.0%)
This commit is contained in:
Christopher Haster
2025-01-06 23:21:42 -06:00
parent 1b3054db89
commit 0617244aa3
3 changed files with 0 additions and 807 deletions
-758
View File
@@ -70,136 +70,6 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# test that we can change flags after mount
[cases.test_gc_setflags]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "spider",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
assert(lfs.omdirs != &lfs.gc.t.o.o);
// change flags
lfsr_gc_setflags(&lfs, GC_FLAGS | LFS_GC_LOOKAHEAD) => 0;
// run GC until we make progress
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
LFS_ASSERT(i < 2*BLOCK_COUNT);
lfsr_gc(&lfs) => 0;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
if (!(fsinfo.flags & LFS_I_CANLOOKAHEAD)) {
break;
}
}
// check the file contents
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that we can change steps after mount
[cases.test_gc_setsteps]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
LFS_GC_LOOKAHEAD
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
defines.GC_STEPS = 1
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "spider",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
assert(lfs.omdirs != &lfs.gc.t.o.o);
// change steps
lfsr_gc_setsteps(&lfs, -1) => 0;
// run GC
lfsr_gc(&lfs) => 0;
// we should have made progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(!(fsinfo.flags & LFS_I_CANLOOKAHEAD));
// check the file contents
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that lookahead dirtying still works with the GC API
[cases.test_gc_lookahead_mutation]
defines.CKMETA = [false, true]
@@ -276,143 +146,6 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# test that adding flags doesn't break lookahead
[cases.test_gc_lookahead_add_flags]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "spider",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
assert(lfs.omdirs != &lfs.gc.t.o.o);
// run GC one step
lfsr_gc(&lfs) => 0;
assert(lfs.omdirs == &lfs.gc.t.o.o);
// change flags and run GC until our traversal is done
lfsr_gc_setflags(&lfs, GC_FLAGS | LFS_GC_LOOKAHEAD) => 0;
while (lfs.omdirs == &lfs.gc.t.o.o) {
lfsr_gc(&lfs) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
// check the file contents
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that removing flags invalidates lookahead
[cases.test_gc_lookahead_remove_flags]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
LFS_GC_LOOKAHEAD
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "spider",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
assert(lfs.omdirs != &lfs.gc.t.o.o);
// run GC one step
lfsr_gc(&lfs) => 0;
assert(lfs.omdirs == &lfs.gc.t.o.o);
// change flags and run GC until our traversal is done
lfsr_gc_setflags(&lfs, GC_FLAGS & ~LFS_GC_LOOKAHEAD) => 0;
while (lfs.omdirs == &lfs.gc.t.o.o) {
lfsr_gc(&lfs) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
// check the file contents
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that compact can make progress in isolation
[cases.test_gc_compact_progress]
@@ -604,197 +337,6 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# test that adding flags doesn't break compact
[cases.test_gc_compact_add_flags]
defines.LOOKAHEAD = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
# set compact thresh to minimum
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// write to our mdir until >gc_compact_thresh full
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jellyfish",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
// hack, don't use the internals like this
uint8_t wbuf[SIZE];
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
lfsr_file_rewind(&lfs, &file) => 0;
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file) => 0;
}
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
assert(lfs.omdirs != &lfs.gc.t.o.o);
// run GC one traversal + one step
while (true) {
lfsr_gc(&lfs) => 0;
// internal traversal done?
if (lfs.omdirs != &lfs.gc.t.o.o) {
break;
}
}
lfsr_gc(&lfs) => 0;
assert(lfs.omdirs == &lfs.gc.t.o.o);
// change flags and run GC until our traversal is done (twice for compact)
lfsr_gc_setflags(&lfs, GC_FLAGS | LFS_GC_COMPACT) => 0;
while (lfs.omdirs == &lfs.gc.t.o.o) {
lfsr_gc(&lfs) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
// check we can still read the file
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
}
lfsr_file_rewind(&lfs, &file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that removing flags invalidates compact
[cases.test_gc_compact_remove_flags]
defines.LOOKAHEAD = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
LFS_GC_COMPACT
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
# set compact thresh to minimum
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// write to our mdir until >gc_compact_thresh full
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jellyfish",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
// hack, don't use the internals like this
uint8_t wbuf[SIZE];
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
lfsr_file_rewind(&lfs, &file) => 0;
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file) => 0;
}
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
assert(lfs.omdirs != &lfs.gc.t.o.o);
// run GC one traversal + one step
while (true) {
lfsr_gc(&lfs) => 0;
// internal traversal done?
if (lfs.omdirs != &lfs.gc.t.o.o) {
break;
}
}
lfsr_gc(&lfs) => 0;
assert(lfs.omdirs == &lfs.gc.t.o.o);
// change flags and run GC until our traversal is done (twice for compact)
lfsr_gc_setflags(&lfs, GC_FLAGS & ~LFS_GC_COMPACT) => 0;
while (lfs.omdirs == &lfs.gc.t.o.o) {
lfsr_gc(&lfs) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
// check we can still read the file
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
}
lfsr_file_rewind(&lfs, &file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that mkconsistent can make progress in isolation
[cases.test_gc_mkconsistent_progress]
@@ -1119,233 +661,6 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# test that adding flags doesn't break mkconsistent
[cases.test_gc_mkconsistent_add_flags]
defines.LOOKAHEAD = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((COMPACT) ? LFS_GC_COMPACT : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
defines.SIZE = 'FILE_BUFFER_SIZE/2'
# <=2 => grm-able
# >2 => requires orphans
defines.ORPHANS = [3, 100]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create two files
lfsr_file_t file1;
lfsr_file_open(&lfs, &file1, "cuttlefish",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf1[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file1) => 0;
lfsr_file_t file2;
lfsr_file_open(&lfs, &file2, "octopus",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf2[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file2) => 0;
// create this many orphaned files
//
// anytime we close a not-yet-created desync file, we create an
// orphan, but note we need these to be different files, and we need
// to close them after all open calls, otherwise we just end up with
// one orphan (littlefs is eager to clean up orphans)
//
lfsr_file_t orphans[ORPHANS];
for (lfs_size_t i = 0; i < ORPHANS; i++) {
char name[256];
sprintf(name, "jellyfish%03x", i);
lfsr_file_open(&lfs, &orphans[i], name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
}
for (lfs_size_t i = 0; i < ORPHANS; i++) {
lfsr_file_close(&lfs, &orphans[i]) => 0;
}
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_INCONSISTENT);
assert(lfs.omdirs != &lfs.gc.t.o.o);
// run GC one step
lfsr_gc(&lfs) => 0;
assert(lfs.omdirs == &lfs.gc.t.o.o);
// change flags and run GC until our traversal is done
lfsr_gc_setflags(&lfs, GC_FLAGS | LFS_GC_MKCONSISTENT) => 0;
while (lfs.omdirs == &lfs.gc.t.o.o) {
lfsr_gc(&lfs) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_INCONSISTENT);
// check we can still read the files
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_file_close(&lfs, &file1) => 0;
lfsr_file_close(&lfs, &file2) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
}
lfsr_file_rewind(&lfs, &file1) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
lfsr_file_rewind(&lfs, &file2) => 0;
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
}
lfsr_file_close(&lfs, &file1) => 0;
lfsr_file_close(&lfs, &file2) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that removing flags invalidates mkconsistent
[cases.test_gc_mkconsistent_remove_flags]
defines.LOOKAHEAD = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
LFS_GC_MKCONSISTENT
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((COMPACT) ? LFS_GC_COMPACT : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
defines.SIZE = 'FILE_BUFFER_SIZE/2'
# <=2 => grm-able
# >2 => requires orphans
defines.ORPHANS = [3, 100]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create two files
lfsr_file_t file1;
lfsr_file_open(&lfs, &file1, "cuttlefish",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf1[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file1) => 0;
lfsr_file_t file2;
lfsr_file_open(&lfs, &file2, "octopus",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf2[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file2) => 0;
// create this many orphaned files
//
// anytime we close a not-yet-created desync file, we create an
// orphan, but note we need these to be different files, and we need
// to close them after all open calls, otherwise we just end up with
// one orphan (littlefs is eager to clean up orphans)
//
lfsr_file_t orphans[ORPHANS];
for (lfs_size_t i = 0; i < ORPHANS; i++) {
char name[256];
sprintf(name, "jellyfish%03x", i);
lfsr_file_open(&lfs, &orphans[i], name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
}
for (lfs_size_t i = 0; i < ORPHANS; i++) {
lfsr_file_close(&lfs, &orphans[i]) => 0;
}
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_INCONSISTENT);
assert(lfs.omdirs != &lfs.gc.t.o.o);
// run GC one step
lfsr_gc(&lfs) => 0;
assert(lfs.omdirs == &lfs.gc.t.o.o);
// change flags and run GC until our traversal is done
lfsr_gc_setflags(&lfs, GC_FLAGS & ~LFS_GC_MKCONSISTENT) => 0;
while (lfs.omdirs == &lfs.gc.t.o.o) {
lfsr_gc(&lfs) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_INCONSISTENT);
// check we can still read the files
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_file_close(&lfs, &file1) => 0;
lfsr_file_close(&lfs, &file2) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
}
lfsr_file_rewind(&lfs, &file1) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
lfsr_file_rewind(&lfs, &file2) => 0;
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
}
lfsr_file_close(&lfs, &file1) => 0;
lfsr_file_close(&lfs, &file2) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test we can detect at least fully clobbered blocks
#
@@ -1800,79 +1115,6 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# pseudo-fuzz test that adding/removing flags doesn't break anything
[cases.test_gc_changing_flags]
defines.N = 100
defines.MKCONSISTENT = [false, true]
defines.LOOKAHEAD = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GC_FLAGS = '''
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((COMPACT) ? LFS_GC_COMPACT : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)
'''
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
# set compact thresh to minimum
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
ifdef = 'LFS_GC'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "spider",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
for (uint32_t i = 0; i < N; i++) {
// rewrite the file every gc cycle
lfsr_file_open(&lfs, &file, "spider",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// choose a new subset of flags every cycle
uint32_t flags = GC_FLAGS & TEST_PRNG(&prng);
lfsr_gc_setflags(&lfs, flags) => 0;
// gc!
lfsr_gc(&lfs) => 0;
}
// check the file contents
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# many/fuzz tests mixed with GC