Files
littlefs/tests/test_mtree.toml
T
Christopher Haster 20c036038a Some small tweaks to tests
- Adopted -1 as a cheap way to mark rbyds as unerased.

- Replaced literal references to alphas (alphas[0 % 26]) with their
  actual characters.
2023-08-22 00:15:23 -05:00

4429 lines
142 KiB
TOML

# Test the high-level metadata tree in the core of littlefs
after = ['test_rbyd', 'test_btree']
# maximize lookahead buffer, we don't actually gc so we only get one pass
# of the disk for these tests
defines.LOOKAHEAD_SIZE = 'BLOCK_COUNT / 8'
# some helper functions
in = 'lfs.c'
code = '''
static lfs_ssize_t lfsr_mdir_get(lfs_t *lfs, const lfsr_mdir_t *mdir,
lfs_ssize_t rid, lfsr_tag_t tag, void *buffer, lfs_size_t size) {
lfsr_data_t data;
int err = lfsr_mdir_lookup(lfs, mdir, rid, tag, NULL, &data);
if (err) {
return err;
}
return lfsr_data_read(lfs, &data, buffer, size);
}
'''
# test a single mroot
[cases.test_mtree_mroot]
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test a single mroot with attributes
[cases.test_mtree_mroot_attrs]
defines.N = [1, 3]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
for (lfs_size_t i = 0; i < N; i++) {
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(i), 0, BUF(&alphas[i % 26], 1)))) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
uint8_t buffer[1];
lfsr_mdir_get(&lfs, &lfs.mroot,
-1, LFSR_TAG_UATTR(i), buffer, 1) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
for (lfs_size_t i = 0; i < N; i++) {
uint8_t buffer[1];
lfsr_mdir_get(&lfs, &lfs.mroot,
-1, LFSR_TAG_UATTR(i), buffer, 1) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test a single mroot with forced compaction
[cases.test_mtree_mroot_compact]
defines.N = [1, 3]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
for (lfs_size_t i = 0; i < N; i++) {
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(i), 0, BUF(&alphas[i % 26], 1)))) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
uint8_t buffer[1];
lfsr_mdir_get(&lfs, &lfs.mroot,
-1, LFSR_TAG_UATTR(i), buffer, 1) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
for (lfs_size_t i = 0; i < N; i++) {
uint8_t buffer[1];
lfsr_mdir_get(&lfs, &lfs.mroot,
-1, LFSR_TAG_UATTR(i), buffer, 1) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test a single mroot with many commits
[cases.test_mtree_mroot_many_commits]
defines.N = [5, 5000]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
for (lfs_size_t i = 0; i < N; i++) {
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(&alphas[i % 26], 1)))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &lfs.mroot,
-1, LFSR_TAG_UATTR(1), buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &lfs.mroot,
-1, LFSR_TAG_UATTR(1), buffer, 4) => 1;
assert(memcmp(buffer, &alphas[(N-1) % 26], 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_get(&lfs, &lfs.mroot,
-1, LFSR_TAG_UATTR(1), buffer, 4) => 1;
assert(memcmp(buffer, &alphas[(N-1) % 26], 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
## Splitting operations ##
# specific split corner cases
[cases.test_mtree_uninline]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_uninline_split]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our entries are still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_split]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
// assert mdir was split correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was split correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
# try creating a range of entries that may or may not split our mtree
[cases.test_mtree_split_many]
defines.N = [5, 10, 20, 40, 80, 160, 320]
defines.FORCE_COMPACTION = [false, true]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create entries
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(lfsr_mtree_weight(&lfs)-1, -1),
&mdir) => 0;
mdir.mid.rid = 0;
for (lfs_size_t i = 0; i < N; i++) {
// force a compaction?
if (FORCE_COMPACTION) {
mdir.u.r.rbyd.eoff = -1;
lfs.mroot.u.r.rbyd.eoff = -1;
}
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, +1,
BUF(&alphas[i % 26], 1)))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
mdir.mid.rid += 1;
}
// try looking up each entry
lfs_size_t i = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
i += 1;
}
}
assert(i == N);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// try looking up each entry
i = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
i += 1;
}
}
assert(i == N);
lfsr_unmount(&lfs) => 0;
'''
# create random entries
[cases.test_mtree_split_fuzz]
defines.N = [5, 10, 20, 40, 80, 160]
defines.FORCE_COMPACTION = [false, true]
defines.SEED = 'range(100)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// at least keep track of the number of entries we expect
lfs_size_t count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random mid
lfs_ssize_t mid = lfsr_mtree_weight(&lfs) == 0
? -1
: (lfs_ssize_t)(TEST_PRNG(&prng) % lfsr_mtree_weight(&lfs));
// fetch mdir
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// choose a pseudo-random rid
mdir.mid.rid = TEST_PRNG(&prng) % (mdir.u.m.weight+1);
// force a compaction?
if (FORCE_COMPACTION) {
mdir.u.r.rbyd.eoff = -1;
lfs.mroot.u.r.rbyd.eoff = -1;
}
// add to rbyd, potentially splitting the mdir
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, +1,
BUF(&alphas[i % 26], 1)))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
count += 1;
}
// try looking up each entry
lfs_size_t count_ = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
count_ += 1;
}
}
// the mtree is a bit difficult to simulate, but we can at least test
// we ended up with the right number of entries
assert(count_ == count);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// try looking up each entry
count_ = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
count_ += 1;
}
}
// the mtree is a bit difficult to simulate, but we can at least test
// we ended up with the right number of entries
assert(count_ == count);
lfsr_unmount(&lfs) => 0;
'''
## Dropping operations ##
# specific drop corner cases
[cases.test_mtree_drop]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// remove the entry, forcing the mdir to be dropped
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_compact]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// remove the entry, forcing the mdir to be dropped
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
// force mdir to compact while we're removing
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_uninline]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
// remove the entry as we compact, forcing the mdir to be dropped
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_uninline_split_l]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an mdir that needs to be split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
// remove the left entry as we compact, forcing the left
// mdir to be dropped
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that one entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_uninline_split_r]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an mdir that needs to be split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
// remove the right entry as we compact, forcing the right mdir
// to be dropped
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that one entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_uninline_split_both]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an mdir that needs to be split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
// remove both entries as we compact, forcing both mdirs to be dropped
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL),
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_split_l]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
// remove the left entry as we compact, forcing the left
// mdir to be dropped
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_split_r]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
// remove the right entry as we compact, forcing the right
// mdir to be dropped
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_split_both]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
// remove both entries as we compact, forcing both mdirs to be dropped
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL),
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
# try creating an mtree and then dropping mdirs
[cases.test_mtree_drop_many]
defines.N = [5, 10, 20, 40, 80, 160, 320]
defines.REMAINING = [20, 5, 1, 0]
if = 'N > REMAINING'
defines.FORCE_COMPACTION = [false, true]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create entries
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(lfsr_mtree_weight(&lfs)-1, -1),
&mdir) => 0;
mdir.mid.rid = 0;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, +1,
BUF(&alphas[i % 26], 1)))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
mdir.mid.rid += 1;
}
// remove entries
for (lfs_size_t i = 0; i < N - REMAINING; i++) {
lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid.bid == -1 || mdir.u.m.weight > 0);
// force a compaction?
if (FORCE_COMPACTION) {
mdir.u.r.rbyd.eoff = -1;
lfs.mroot.u.r.rbyd.eoff = -1;
}
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
}
// try looking up each entry
lfs_size_t i = N - REMAINING;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
i += 1;
}
}
assert(i == N);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// try looking up each entry
i = N - REMAINING;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
i += 1;
}
}
assert(i == N);
lfsr_unmount(&lfs) => 0;
'''
# this one has some pretty nasty corner cases
[cases.test_mtree_repeated_drop]
defines.N = [5, 10, 20, 40]
defines.FORCE_COMPACTION = [false, true]
defines.CYCLES = 10
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
for (lfs_size_t cycle = 0; cycle < CYCLES; cycle++) {
// create entries
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(lfsr_mtree_weight(&lfs)-1, -1),
&mdir) => 0;
mdir.mid.rid = 0;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, +1,
BUF(&alphas[i % 26], 1)))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
mdir.mid.rid += 1;
}
// try looking up each entry
lfs_size_t i = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
i += 1;
}
}
assert(i == N);
// remove entries
for (lfs_size_t i = 0; i < N; i++) {
lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid.bid == -1 || mdir.u.m.weight > 0);
// force a compaction?
if (FORCE_COMPACTION) {
mdir.u.r.rbyd.eoff = -1;
lfs.mroot.u.r.rbyd.eoff = -1;
}
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
}
assert(lfsr_mtree_weight(&lfs) == 0);
assert(lfs.mroot.u.m.weight == 0);
}
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
assert(lfsr_mtree_weight(&lfs) == 0);
assert(lfs.mroot.u.m.weight == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_fuzz]
defines.N = [5, 10, 20, 40, 80, 160]
defines.FORCE_COMPACTION = [false, true]
defines.SEED = 'range(100)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// at least keep track of the number of entries we expect
lfs_size_t count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random mid
lfs_ssize_t mid = lfsr_mtree_weight(&lfs) == 0
? -1
: (lfs_ssize_t)(TEST_PRNG(&prng) % lfsr_mtree_weight(&lfs));
// fetch mdir
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// choose a pseudo-random rid
mdir.mid.rid = TEST_PRNG(&prng) % (mdir.u.m.weight+1);
// choose to create or delete
uint8_t op = (lfs_size_t)mdir.mid.rid == mdir.u.m.weight
? 0
: TEST_PRNG(&prng) % 2;
// force a compaction?
if (FORCE_COMPACTION) {
mdir.u.r.rbyd.eoff = -1;
lfs.mroot.u.r.rbyd.eoff = -1;
}
// create
if (op == 0) {
// add to rbyd, potentially splitting the mdir
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, +1,
BUF(&alphas[i % 26], 1)))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
count += 1;
// delete
} else {
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, RM, -1, NULL))) => 0;
count -= 1;
}
}
// try looking up each entry
lfs_size_t count_ = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid.bid == -1 || mdir.u.m.weight > 0);
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
count_ += 1;
}
}
// the mtree is a bit difficult to simulate, but we can at least test
// we ended up with the right number of entries
assert(count_ == count);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// try looking up each entry
count_ = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid.bid == -1 || mdir.u.m.weight > 0);
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
count_ += 1;
}
}
// the mtree is a bit difficult to simulate, but we can at least test
// we ended up with the right number of entries
assert(count_ == count);
lfsr_unmount(&lfs) => 0;
'''
## Relocation operations ##
# specific relocation corner cases
[cases.test_mtree_relocate]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mtree has one mdir
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// force mdir to compact twice, this should relocate
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_t old_mdir = mdir;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, REG, 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mtree has one mdir
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_relocate_sibling_l]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// force mdir to compact twice, this should relocate
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_t old_mdir = mdir;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, REG, 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_relocate_sibling_r]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// force mdir to compact twice, this should relocate
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_t old_mdir = mdir;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, REG, 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_extend_twice]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
# force our block to compact by setting prog_size=block_size, we don't have
# any way to indirectly force the intermediary mroots to compact otherwise
defines.PROG_SIZE = 'BLOCK_SIZE'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// force mroot to compact 2x2 times, this should extend the mroot twice
lfsr_mdir_t old_mroot = lfs.mroot;
for (int i = 0; i < 4; i++) {
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
}
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_relocate_mroot]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// force mroot to compact twice again, this should relocate the mroot
old_mroot = lfs.mroot;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_relocate_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mtree has one mdir
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// setup mroot to need to compact, this should trigger a relocation when
// we relocate the mdir below
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_t old_mroot = lfs.mroot;
// force mdir to compact twice, this should relocate
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_t old_mdir = mdir;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, REG, 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated our mdir
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mtree has one mdir
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mdir
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_split_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// setup mroot to need to compact, this should trigger a relocation when
// we relocate the mdir below
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_t old_mroot = lfs.mroot;
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
// assert mdir was split correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was split correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// setup mroot to need to compact, this should trigger a relocation when
// we relocate the mdir below
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_t old_mroot = lfs.mroot;
// remove the entry, forcing the mdir to be dropped
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_uninline_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// force mroot to compact once, so the second compact below will trigger
// a relocation
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact, this should trigger a relocation
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_uninline_split_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// force mroot to compact once, so the second compact below will trigger
// a relocation
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact, this should trigger a relocation
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our entries are still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
# this fuzz covers a lot of configuratinos
[cases.test_mtree_relocating_fuzz]
defines.N = [5, 10, 20, 40]
defines.FORCE_COMPACTION = [false, true]
defines.BLOCK_CYCLES = [5, 2, 1]
defines.SEED = 'range(500)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// at least keep track of the number of entries we expect
lfs_size_t count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random mid
lfs_ssize_t mid = lfsr_mtree_weight(&lfs) == 0
? -1
: (lfs_ssize_t)(TEST_PRNG(&prng) % lfsr_mtree_weight(&lfs));
// fetch mdir
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// choose a pseudo-random rid
mdir.mid.rid = TEST_PRNG(&prng) % (mdir.u.m.weight+1);
// choose to create or delete
uint8_t op = (lfs_size_t)mdir.mid.rid == mdir.u.m.weight
? 0
: TEST_PRNG(&prng) % 3;
// force a compaction?
if (FORCE_COMPACTION) {
mdir.u.r.rbyd.eoff = -1;
lfs.mroot.u.r.rbyd.eoff = -1;
}
// create
if (op == 0) {
// add to rbyd
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, +1,
BUF(&alphas[i % 26], 1)))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
count += 1;
// update
} else if (op == 1) {
// update rbyd
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, 0,
BUF(&alphas[i % 26], 1)))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
// delete
} else {
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, RM, -1, NULL))) => 0;
count -= 1;
}
}
// try looking up each entry
lfs_size_t count_ = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid.bid == -1 || mdir.u.m.weight > 0);
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
count_ += 1;
}
}
// the mtree is a bit difficult to simulate, but we can at least test
// we ended up with the right number of entries
assert(count_ == count);
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, CFG) => 0;
// try looking up each entry
count_ = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid.bid == -1 || mdir.u.m.weight > 0);
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
count_ += 1;
}
}
// the mtree is a bit difficult to simulate, but we can at least test
// we ended up with the right number of entries
assert(count_ == count);
lfsr_unmount(&lfs) => 0;
'''
## Neighboring mdir updates ##
[cases.test_mtree_neighbor]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {
.mdir={.mid=LFSR_MID(-1, 0), .u=lfs.mroot.u}};
lfsr_openedmdir_t right_neighbor = {
.mdir={.mid=LFSR_MID(-1, 1), .u=lfs.mroot.u}};
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// insert a new entry, this should update our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF("c", 1)))) => 0;
// assert that our entry is still in the mtree
assert(lfs.mroot.u.m.weight == 3);
uint8_t buffer[1];
lfsr_mdir_get(&lfs, &lfs.mroot, 1, LFSR_TAG_REG,
buffer, 1) => 1;
assert(memcmp(buffer, "c", 1) == 0);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == -1);
assert(left_neighbor.mdir.mid.rid == 0);
assert(memcmp(&left_neighbor.mdir.u, &lfs.mroot.u,
sizeof(lfs.mroot.u)) == 0);
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
assert(right_neighbor.mdir.mid.bid == -1);
assert(right_neighbor.mdir.mid.rid == 2);
assert(memcmp(&right_neighbor.mdir.u, &lfs.mroot.u,
sizeof(lfs.mroot.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_remove_l]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {
.mdir={.mid=LFSR_MID(-1, 0), .u=lfs.mroot.u}};
lfsr_openedmdir_t right_neighbor = {
.mdir={.mid=LFSR_MID(-1, 1), .u=lfs.mroot.u}};
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// try removing our left entry
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// assert that an entry was removed
assert(lfs.mroot.u.m.weight == 1);
// assert that our neighbors were updated correctly
assert(lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
assert(right_neighbor.mdir.mid.bid == -1);
assert(right_neighbor.mdir.mid.rid == 0);
assert(memcmp(&right_neighbor.mdir.u, &lfs.mroot.u,
sizeof(lfs.mroot.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_remove_r]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {
.mdir={.mid=LFSR_MID(-1, 0), .u=lfs.mroot.u}};
lfsr_openedmdir_t right_neighbor = {
.mdir={.mid=LFSR_MID(-1, 1), .u=lfs.mroot.u}};
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// try removing our left entry
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, RM, -1, NULL))) => 0;
// assert that an entry was removed
assert(lfs.mroot.u.m.weight == 1);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == -1);
assert(left_neighbor.mdir.mid.rid == 0);
assert(memcmp(&left_neighbor.mdir.u, &lfs.mroot.u,
sizeof(lfs.mroot.u)) == 0);
assert(lfsr_mdir_isdropped(&right_neighbor.mdir));
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_uninline]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {
.mdir={.mid=LFSR_MID(-1, 0), .u=lfs.mroot.u}};
lfsr_openedmdir_t right_neighbor = {
.mdir={.mid=LFSR_MID(-1, 1), .u=lfs.mroot.u}};
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, 'd', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 2);
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "d", 1) == 0);
// note that our current implementation splits here, which is suboptimal
// but saves on code size
lfsr_mdir_t msibling;
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
assert(msibling.u.m.weight == 1);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == 0);
assert(left_neighbor.mdir.mid.rid == 0);
assert(memcmp(&left_neighbor.mdir.u, &mdir.u,
sizeof(mdir.u)) == 0);
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
assert(right_neighbor.mdir.mid.bid == 1);
assert(right_neighbor.mdir.mid.rid == 0);
assert(memcmp(&right_neighbor.mdir.u, &msibling.u,
sizeof(msibling.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_uninline_split]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {
.mdir={.mid=LFSR_MID(-1, 0), .u=lfs.mroot.u}};
lfsr_openedmdir_t right_neighbor = {
.mdir={.mid=LFSR_MID(-1, 1), .u=lfs.mroot.u}};
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'd', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(2, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our entries are still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 2);
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
lfsr_mdir_t msibling;
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
assert(msibling.u.m.weight == 2);
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "d", 1) == 0);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == 0);
assert(left_neighbor.mdir.mid.rid == 0);
assert(memcmp(&left_neighbor.mdir.u, &mdir.u,
sizeof(mdir.u)) == 0);
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
assert(right_neighbor.mdir.mid.bid == 1);
assert(right_neighbor.mdir.mid.rid == 1);
assert(memcmp(&right_neighbor.mdir.u, &msibling.u,
sizeof(msibling.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_split]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'd', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// setup our neighbors
//
// note we do this after uninlining! this is because uninlining may
// aggresively split the mtree if there are already neighbors in the mdir
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
LFSR_ATTR(2, REG, +1, BUF("b", 1)))) => 0;
// this test only works if these all fit in the mdir
assert(lfsr_mtree_weight(&lfs) == 1);
assert(mdir.u.m.weight == 3);
lfsr_openedmdir_t left_neighbor = {
.mdir={.mid=LFSR_MID(mdir.mid.bid, 0), .u=mdir.u}};
lfsr_openedmdir_t right_neighbor = {
.mdir={.mid=LFSR_MID(mdir.mid.bid, 2), .u=mdir.u}};
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// now add another large entry to the mdir, forcing a split
memset(buffer, 'e', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(2, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
// assert mdir was split correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot still has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 2);
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "d", 1) == 0);
lfsr_mdir_t msibling;
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
assert(msibling.u.m.weight == 2);
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "e", 1) == 0);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == 0);
assert(left_neighbor.mdir.mid.rid == 0);
assert(memcmp(&left_neighbor.mdir.u, &mdir.u,
sizeof(mdir.u)) == 0);
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
assert(right_neighbor.mdir.mid.bid == 1);
assert(right_neighbor.mdir.mid.rid == 1);
assert(memcmp(&right_neighbor.mdir.u, &msibling.u,
sizeof(msibling.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {
.mdir={.mid=LFSR_MID(-1, 0), .u=lfs.mroot.u}};
lfsr_openedmdir_t right_neighbor = {
.mdir={.mid=LFSR_MID(-1, 1), .u=lfs.mroot.u}};
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == -1);
assert(left_neighbor.mdir.mid.rid == 0);
assert(memcmp(&left_neighbor.mdir.u, &lfs.mroot.u,
sizeof(lfs.mroot.u)) == 0);
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
assert(right_neighbor.mdir.mid.bid == -1);
assert(right_neighbor.mdir.mid.rid == 1);
assert(memcmp(&right_neighbor.mdir.u, &lfs.mroot.u,
sizeof(lfs.mroot.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_relocate]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'd', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// setup our neighbors
//
// note we do this after uninlining! this is because uninlining may
// aggresively split the mtree if there are already neighbors in the mdir
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
LFSR_ATTR(2, REG, +1, BUF("b", 1)))) => 0;
// this test only works if these all fit in the mdir
assert(lfsr_mtree_weight(&lfs) == 1);
assert(mdir.u.m.weight == 3);
lfsr_openedmdir_t left_neighbor = {
.mdir={.mid=LFSR_MID(mdir.mid.bid, 0), .u=mdir.u}};
lfsr_openedmdir_t right_neighbor = {
.mdir={.mid=LFSR_MID(mdir.mid.bid, 2), .u=mdir.u}};
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// force mdir to compact twice, this should relocate
lfsr_mdir_t old_mdir = mdir;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'e', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "c", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 3);
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "e", 1) == 0);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == 0);
assert(left_neighbor.mdir.mid.rid == 0);
assert(memcmp(&left_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
assert(right_neighbor.mdir.mid.bid == 0);
assert(right_neighbor.mdir.mid.rid == 2);
assert(memcmp(&right_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_mid_split]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
//// create a situation where we have 3 mdirs in our tree
// first force mroot to uninlined+split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// we should now have 2 mdirs
assert(lfsr_mtree_weight(&lfs) == 2);
// now force one of our siblings to split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
// we should now have 3 mdirs
assert(lfsr_mtree_weight(&lfs) == 3);
//// Now test splitting updates mids correctly
// setup our neighbors
lfsr_openedmdir_t left_neighbor;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &left_neighbor.mdir) => 0;
assert(left_neighbor.mdir.u.m.weight == 1);
left_neighbor.mdir.mid.rid = 0;
lfsr_openedmdir_t right_neighbor;
lfsr_mtree_lookup(&lfs, LFSR_MID(2, -1), &right_neighbor.mdir) => 0;
assert(right_neighbor.mdir.u.m.weight == 1);
right_neighbor.mdir.mid.rid = 0;
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// cause middle mdir to split
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
memset(buffer, 'd', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
// we should now have 4 mdirs
assert(lfsr_mtree_weight(&lfs) == 4);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == 0);
assert(left_neighbor.mdir.mid.rid == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(memcmp(&left_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
assert(right_neighbor.mdir.mid.bid == 3);
assert(right_neighbor.mdir.mid.rid == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(3, -1), &mdir) => 0;
assert(memcmp(&right_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_mid_drop]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
//// create a situation where we have 3 mdirs in our tree
// first force mroot to uninlined+split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// we should now have 2 mdirs
assert(lfsr_mtree_weight(&lfs) == 2);
// now force one of our siblings to split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
memset(buffer, 'c', SIZE);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mdir to compact
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
// we should now have 3 mdirs
assert(lfsr_mtree_weight(&lfs) == 3);
//// Now test dropping updates mids correctly
// setup our neighbors
lfsr_openedmdir_t left_neighbor;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &left_neighbor.mdir) => 0;
assert(left_neighbor.mdir.u.m.weight == 1);
left_neighbor.mdir.mid.rid = 0;
lfsr_openedmdir_t right_neighbor;
lfsr_mtree_lookup(&lfs, LFSR_MID(2, -1), &right_neighbor.mdir) => 0;
assert(right_neighbor.mdir.u.m.weight == 1);
right_neighbor.mdir.mid.rid = 0;
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
// cause middle mdir to drop
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// we should now have 2 mdirs
assert(lfsr_mtree_weight(&lfs) == 2);
// assert that our neighbors were updated correctly
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
assert(left_neighbor.mdir.mid.bid == 0);
assert(left_neighbor.mdir.mid.rid == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(memcmp(&left_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
assert(right_neighbor.mdir.mid.bid == 1);
assert(right_neighbor.mdir.mid.rid == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
assert(memcmp(&right_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
## mtree traversal ##
# test specific corner cases
[cases.test_mtree_traversal]
defines.VALIDATE = [false, true]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// insert a new entry, this should update our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF("a", 1)))) => 0;
// assert that our entry is still in the mtree
assert(lfs.mroot.u.m.weight == 1);
uint8_t buffer[1];
lfsr_mdir_get(&lfs, &lfs.mroot, 0, LFSR_TAG_REG,
buffer, 1) => 1;
assert(memcmp(buffer, "a", 1) == 0);
// test that we can traverse the tree, keeping track of all blocks we see
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*1);
lfsr_mid_t mid_;
lfsr_tag_t tag_;
lfsr_data_t data_;
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
&mid_, &tag_, &data_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
mid_.bid,
mid_.rid,
tag_,
branch->block, branch->trunk);
// keep track of seen blocks
seen[branch->block / 8] |= 1 << (branch->block % 8);
} else if (tag_ == LFSR_TAG_MDIR) {
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
mid_.bid,
mid_.rid,
tag_,
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
// keep track of seen blocks
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
} else {
// this shouldn't happen
printf("traversal: %d.%d 0x%x %d\n",
mid_.bid,
mid_.rid,
tag_,
lfsr_data_size(&data_));
assert(false);
}
}
// if traversal worked, we should be able to clobber all other blocks
uint8_t buffer_[BLOCK_SIZE];
memset(buffer_, 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, buffer_, BLOCK_SIZE) => 0;
}
}
free(seen);
// and the tree should still work
// assert that our entry is still in the mtree
assert(lfs.mroot.u.m.weight == 1);
lfsr_mdir_get(&lfs, &lfs.mroot, 0, LFSR_TAG_REG,
buffer, 1) => 1;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_traversal_uninline]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
defines.VALIDATE = [false, true]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
// test that we can traverse the tree, keeping track of all blocks we see
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*2);
lfsr_mid_t mid_;
lfsr_tag_t tag_;
lfsr_data_t data_;
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
&mid_, &tag_, &data_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
mid_.bid,
mid_.rid,
tag_,
branch->block, branch->trunk);
// keep track of seen blocks
seen[branch->block / 8] |= 1 << (branch->block % 8);
} else if (tag_ == LFSR_TAG_MDIR) {
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
mid_.bid,
mid_.rid,
tag_,
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
// keep track of seen blocks
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
} else {
// this shouldn't happen
printf("traversal: %d.%d 0x%x %d\n",
mid_.bid,
mid_.rid,
tag_,
lfsr_data_size(&data_));
assert(false);
}
}
// if traversal worked, we should be able to clobber all other blocks
uint8_t buffer_[BLOCK_SIZE];
memset(buffer_, 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, buffer_, BLOCK_SIZE) => 0;
}
}
free(seen);
// and the tree should still work
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_traversal_split]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
defines.VALIDATE = [false, true]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
// force mroot to compact
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our entries are still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_mdir_t msibling;
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
assert(msibling.u.m.weight == 1);
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
// test that we can traverse the tree, keeping track of all blocks we see
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*3);
lfsr_mid_t mid_;
lfsr_tag_t tag_;
lfsr_data_t data_;
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
&mid_, &tag_, &data_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
mid_.bid,
mid_.rid,
tag_,
branch->block, branch->trunk);
// keep track of seen blocks
seen[branch->block / 8] |= 1 << (branch->block % 8);
} else if (tag_ == LFSR_TAG_MDIR) {
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
mid_.bid,
mid_.rid,
tag_,
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
// keep track of seen blocks
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
} else {
// this shouldn't happen
printf("traversal: %d.%d 0x%x %d\n",
mid_.bid,
mid_.rid,
tag_,
lfsr_data_size(&data_));
assert(false);
}
}
// if traversal worked, we should be able to clobber all other blocks
uint8_t buffer_[BLOCK_SIZE];
memset(buffer_, 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, buffer_, BLOCK_SIZE) => 0;
}
}
free(seen);
// and the tree should still work
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.u.m.weight == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
assert(mdir.u.m.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "a", 1) == 0);
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
assert(msibling.u.m.weight == 1);
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_REG,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_traversal_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
defines.VALIDATE = [false, true]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
// test that we can traverse the tree, keeping track of all blocks we see
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*3);
lfsr_mid_t mid_;
lfsr_tag_t tag_;
lfsr_data_t data_;
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
&mid_, &tag_, &data_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
mid_.bid,
mid_.rid,
tag_,
branch->block, branch->trunk);
// keep track of seen blocks
seen[branch->block / 8] |= 1 << (branch->block % 8);
} else if (tag_ == LFSR_TAG_MDIR) {
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
mid_.bid,
mid_.rid,
tag_,
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
// keep track of seen blocks
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
} else {
// this shouldn't happen
printf("traversal: %d.%d 0x%x %d\n",
mid_.bid,
mid_.rid,
tag_,
lfsr_data_size(&data_));
assert(false);
}
}
// if traversal worked, we should be able to clobber all other blocks
uint8_t buffer_[BLOCK_SIZE];
memset(buffer_, 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, buffer_, BLOCK_SIZE) => 0;
}
}
free(seen);
// and the tree should still work
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
'''
# larger traversal tests
[cases.test_mtree_traversal_many]
defines.N = [5, 10, 20, 40, 80, 160, 320]
defines.VALIDATE = [false, true]
defines.FORCE_COMPACTION = [false, true]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// create entries
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(lfsr_mtree_weight(&lfs)-1, -1),
&mdir) => 0;
mdir.mid.rid = 0;
for (lfs_size_t i = 0; i < N; i++) {
// force a compaction?
if (FORCE_COMPACTION) {
mdir.u.r.rbyd.eoff = -1;
lfs.mroot.u.r.rbyd.eoff = -1;
}
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, +1,
BUF(&alphas[i % 26], 1)))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
mdir.mid.rid += 1;
}
// try looking up each entry
lfs_size_t i = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
i += 1;
}
}
assert(i == N);
// test that we can traverse the tree, keeping track of all blocks we see
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*(1+N));
lfsr_mid_t mid_;
lfsr_tag_t tag_;
lfsr_data_t data_;
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
&mid_, &tag_, &data_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
mid_.bid,
mid_.rid,
tag_,
branch->block, branch->trunk);
// keep track of seen blocks
seen[branch->block / 8] |= 1 << (branch->block % 8);
} else if (tag_ == LFSR_TAG_MDIR) {
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
mid_.bid,
mid_.rid,
tag_,
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
// keep track of seen blocks
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
} else {
// this shouldn't happen
printf("traversal: %d.%d 0x%x %d\n",
mid_.bid,
mid_.rid,
tag_,
lfsr_data_size(&data_));
assert(false);
}
}
// if traversal worked, we should be able to clobber all other blocks
uint8_t buffer_[BLOCK_SIZE];
memset(buffer_, 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, buffer_, BLOCK_SIZE) => 0;
}
}
free(seen);
// and the tree should still work
// try looking up each entry
i = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
i += 1;
}
}
assert(i == N);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_traversal_fuzz]
defines.N = [5, 10, 20, 40, 80, 160]
defines.VALIDATE = [false, true]
defines.FORCE_COMPACTION = [false, true]
defines.SEED = 'range(100)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// remove root dstart for now
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(0, RM, -1, NULL))) => 0;
// at least keep track of the number of entries we expect
lfs_size_t count = 0;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random mid
lfs_ssize_t mid = lfsr_mtree_weight(&lfs) == 0
? -1
: (lfs_ssize_t)(TEST_PRNG(&prng) % lfsr_mtree_weight(&lfs));
// fetch mdir
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
// choose a pseudo-random rid
mdir.mid.rid = TEST_PRNG(&prng) % (mdir.u.m.weight+1);
// force a compaction?
if (FORCE_COMPACTION) {
mdir.u.r.rbyd.eoff = -1;
lfs.mroot.u.r.rbyd.eoff = -1;
}
// add to rbyd, potentially splitting the mdir
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid.rid, REG, +1,
BUF(&alphas[i % 26], 1)))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
count += 1;
}
// try looking up each entry
lfs_size_t count_ = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
count_ += 1;
}
}
// the mtree is a bit difficult to simulate, but we can at least test
// we ended up with the right number of entries
assert(count_ == count);
// test that we can traverse the tree, keeping track of all blocks
// we see
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*(1+N));
lfsr_mid_t mid_;
lfsr_tag_t tag_;
lfsr_data_t data_;
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
&mid_, &tag_, &data_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
mid_.bid,
mid_.rid,
tag_,
branch->block, branch->trunk);
// keep track of seen blocks
seen[branch->block / 8] |= 1 << (branch->block % 8);
} else if (tag_ == LFSR_TAG_MDIR) {
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
mid_.bid,
mid_.rid,
tag_,
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
// keep track of seen blocks
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
} else {
// this shouldn't happen
printf("traversal: %d.%d 0x%x %d\n",
mid_.bid,
mid_.rid,
tag_,
lfsr_data_size(&data_));
assert(false);
}
}
// if traversal worked, we should be able to clobber all other blocks
uint8_t buffer_[BLOCK_SIZE];
memset(buffer_, 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, buffer_, BLOCK_SIZE) => 0;
}
}
free(seen);
// and the tree should still work
// try looking up each entry
count_ = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
for (mdir.mid.rid = 0;
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
mdir.mid.rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
buffer, 4) => 1;
count_ += 1;
}
}
// the mtree is a bit difficult to simulate, but we can at least test
// we ended up with the right number of entries
assert(count_ == count);
lfsr_unmount(&lfs) => 0;
'''
## Cycle detection? ##
# test that our cycle detector at least works in common cases
[cases.test_mtree_traversal_mroot_cycle]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
uint8_t buffer[LFSR_MDIR_DSIZE];
lfs_ssize_t d = lfsr_mdir_todisk(&lfs, LFSR_MDIR_MROOTANCHOR, buffer);
assert(d >= 0);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, MROOT, 0, BUF(buffer, d)))) => 0;
// technically, cycle detection only needs to work when we're validating
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
LFSR_MTREE_TRAVERSAL_VALIDATE);
for (lfs_block_t i = 0;; i++) {
// assert that we detect the cycle in a reasonable number of iterations
assert(i < 1024);
lfsr_mid_t mid_;
lfsr_tag_t tag_;
lfsr_data_t data_;
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
&mid_, &tag_, &data_);
assert(!err || err == LFS_ERR_CORRUPT);
if (err == LFS_ERR_CORRUPT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
mid_.bid,
mid_.rid,
tag_,
branch->block, branch->trunk);
} else if (tag_ == LFSR_TAG_MDIR) {
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
mid_.bid,
mid_.rid,
tag_,
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
} else {
// this shouldn't happen
printf("traversal: %d.%d 0x%x %d\n",
mid_.bid,
mid_.rid,
tag_,
lfsr_data_size(&data_));
assert(false);
}
}
lfsr_unmount(&lfs) => 0;
'''
## Magic consistency ##
# make sure our magic string ("littlefs") shows up in the same place (off=8)
[cases.test_mtree_magic]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// check our magic string
//
// note if we lose power we may not have the magic string in both blocks!
// but we don't lose power in this test so we can assert the magic string
// is present in both
uint8_t magic[lfs_max(16, READ_SIZE)];
CFG->read(CFG, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
CFG->read(CFG, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
'''
[cases.test_mtree_magic_extend]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check our magic string
//
// note if we lose power we may not have the magic string in both blocks!
// but we don't lose power in this test so we can assert the magic string
// is present in both
uint8_t magic[lfs_max(16, READ_SIZE)];
CFG->read(CFG, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
CFG->read(CFG, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
'''
[cases.test_mtree_magic_extend_twice]
# this should be set so only one entry can fit in a metadata block
defines.SIZE = 'BLOCK_SIZE / 4'
# make it so blocks relocate every two compacts
defines.BLOCK_CYCLES = 2
# force our block to compact by setting prog_size=block_size, we don't have
# any way to indirectly force the intermediary mroots to compact otherwise
defines.PROG_SIZE = 'BLOCK_SIZE'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
lfs_alloc_ack(&lfs);
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, 'a', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// force mroot to compact 2x2 times, this should extend the mroot twice
lfsr_mdir_t old_mroot = lfs.mroot;
for (int i = 0; i < 4; i++) {
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
}
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
memset(buffer, 'b', SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
// assert that our attr is still in the mroot
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
buffer, SIZE) => SIZE;
assert(memcmp(buffer, "b", 1) == 0);
lfsr_unmount(&lfs) => 0;
// check our magic string
//
// note if we lose power we may not have the magic string in both blocks!
// but we don't lose power in this test so we can assert the magic string
// is present in both
uint8_t magic[lfs_max(16, READ_SIZE)];
CFG->read(CFG, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
CFG->read(CFG, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
'''