Files
littlefs/tests/test_mtree.toml
T
Christopher Haster 91d90b7eef Some minor tweaks to internal ptr types
- Renamed mpair -> mptr, may have >2 blocks in the future.

- Renamed branch -> bptr for consistency.

- Renamed other_block -> redund_rbyd.

- Changed comparison functions to use -1, 0, +1, even for unordered
  types.

- Added lfs_cmp function for unioning comparisons with signed errors.
2023-06-27 13:21:22 -05:00

4043 lines
133 KiB
TOML

# 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'
# 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;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(i), 0, &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;
for (lfs_size_t i = 0; i < N; i++) {
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(i), 0, &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;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, &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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert that our entries are still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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.rbyd.weight == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mdir to compact
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, NULL, 0) => 0;
// assert mdir was split correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot still has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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;
// create entries
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, lfsr_mtree_weight(&lfs)-1, &mdir) => 0;
lfs_ssize_t rid = 0;
for (lfs_size_t i = 0; i < N; i++) {
// force a compaction?
if (FORCE_COMPACTION) {
mdir.rbyd.off = cfg->block_size;
lfs.mroot.rbyd.off = cfg->block_size;
}
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, +1, &alphas[i % 26], 1))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
rid += 1;
}
// try looking up each entry
lfs_size_t i = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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.SAMPLES = 100
# -1 => all pseudo-random seeds
# n => reproduce a specific seed
defines.SEED = -1
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = (SEED == -1 ? 1 : SEED);
(SEED == -1 ? seed < SAMPLES+1 : seed == SEED);
seed++) {
printf("--- seed: %d ---\n", seed);
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 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, mid, &mdir) => 0;
// choose a pseudo-random rid
lfs_ssize_t rid = TEST_PRNG(&prng) % (lfsr_mdir_weight(&mdir)+1);
// force a compaction?
if (FORCE_COMPACTION) {
mdir.rbyd.off = cfg->block_size;
lfs.mroot.rbyd.off = cfg->block_size;
}
// add to rbyd, potentially splitting the mdir
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, +1, &alphas[i % 26], 1))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// remove the entry, forcing the mdir to be dropped
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 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.rbyd.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, &alphas[0 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// remove the entry, forcing the mdir to be dropped
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
// force mdir to compact while we're removing
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 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.rbyd.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, &alphas[0 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
// remove the entry as we compact, forcing the mdir to be dropped
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 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.rbyd.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, &alphas[0 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an mdir that needs to be split
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
// remove the left entry as we compact, forcing the left
// mdir to be dropped
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert that one entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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.rbyd.weight == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an mdir that needs to be split
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
// remove the right entry as we compact, forcing the right mdir
// to be dropped
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert that one entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 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.rbyd.weight == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an mdir that needs to be split
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
// remove both entries as we compact, forcing both mdirs to be dropped
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0),
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.rbyd.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.rbyd.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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mdir to compact
mdir.rbyd.off = BLOCK_SIZE;
// remove the left entry as we compact, forcing the left
// mdir to be dropped
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mdir to compact
mdir.rbyd.off = BLOCK_SIZE;
// remove the right entry as we compact, forcing the right
// mdir to be dropped
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(1, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that one entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mdir to compact
mdir.rbyd.off = BLOCK_SIZE;
// remove both entries as we compact, forcing both mdirs to be dropped
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0),
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 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.rbyd.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, &alphas[0 % 26], 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;
// create entries
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, lfsr_mtree_weight(&lfs)-1, &mdir) => 0;
lfs_ssize_t rid = 0;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, +1, &alphas[i % 26], 1))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
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, mid, &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid == -1 || mdir.rbyd.weight > 0);
// force a compaction?
if (FORCE_COMPACTION) {
mdir.rbyd.off = cfg->block_size;
lfs.mroot.rbyd.off = cfg->block_size;
}
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
}
// try looking up each entry
lfs_size_t i = N - REMAINING;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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;
for (lfs_size_t cycle = 0; cycle < CYCLES; cycle++) {
// create entries
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, lfsr_mtree_weight(&lfs)-1, &mdir) => 0;
lfs_ssize_t rid = 0;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, +1, &alphas[i % 26], 1))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
rid += 1;
}
// try looking up each entry
lfs_size_t i = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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, mid, &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid == -1 || mdir.rbyd.weight > 0);
// force a compaction?
if (FORCE_COMPACTION) {
mdir.rbyd.off = cfg->block_size;
lfs.mroot.rbyd.off = cfg->block_size;
}
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
}
assert(lfsr_mtree_weight(&lfs) == 0);
assert(lfsr_mdir_weight(&lfs.mroot) == 0);
}
lfsr_unmount(&lfs) => 0;
// check things stay sane after remount
lfsr_mount(&lfs, cfg) => 0;
assert(lfsr_mtree_weight(&lfs) == 0);
assert(lfsr_mdir_weight(&lfs.mroot) == 0);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_drop_fuzz]
defines.N = [5, 10, 20, 40, 80, 160]
defines.FORCE_COMPACTION = [false, true]
defines.SAMPLES = 100
# -1 => all pseudo-random seeds
# n => reproduce a specific seed
defines.SEED = -1
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = (SEED == -1 ? 1 : SEED);
(SEED == -1 ? seed < SAMPLES+1 : seed == SEED);
seed++) {
printf("--- seed: %d ---\n", seed);
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 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, mid, &mdir) => 0;
// choose a pseudo-random rid
lfs_ssize_t rid = TEST_PRNG(&prng) % (lfsr_mdir_weight(&mdir)+1);
// choose to create or delete
uint8_t op = (lfs_size_t)rid == lfsr_mdir_weight(&mdir)
? 0
: TEST_PRNG(&prng) % 2;
// force a compaction?
if (FORCE_COMPACTION) {
mdir.rbyd.off = cfg->block_size;
lfs.mroot.rbyd.off = cfg->block_size;
}
// create
if (op == 0) {
// add to rbyd, potentially splitting the mdir
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, +1,
&alphas[i % 26], 1))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
count += 1;
// delete
} else {
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, UNR, -1, NULL, 0))) => 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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid == -1 || mdir.rbyd.weight > 0);
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid == -1 || mdir.rbyd.weight > 0);
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mtree has one mdir
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// force mdir to compact twice, this should relocate
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_t old_mdir = mdir;
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0;
mdir.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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.rbyd.weight == 0);
// assert we relocated
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// force mdir to compact twice, this should relocate
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_t old_mdir = mdir;
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0;
mdir.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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.rbyd.weight == 0);
// assert we relocated
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// force mdir to compact twice, this should relocate
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_t old_mdir = mdir;
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0;
mdir.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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.rbyd.weight == 0);
// assert we relocated
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
lfs.mroot.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[1 % 26], 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, &lfs.mroot) != 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, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, 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.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
}
lfs.mroot.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[1 % 26], 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, &lfs.mroot) != 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, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
// force mroot to compact twice again, this should relocate the mroot
old_mroot = lfs.mroot;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
lfs.mroot.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[1 % 26], 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, &lfs.mroot) != 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, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mtree has one mdir
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// setup mroot to need to compact, this should trigger a relocation when
// we relocate the mdir below
lfs.mroot.rbyd.off = 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, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_t old_mdir = mdir;
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0;
mdir.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, 0, buffer, SIZE))) => 0;
// assert we relocated our mdir
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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.rbyd.weight == 0);
// assert we relocated our mdir
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// setup mroot to need to compact, this should trigger a relocation when
// we relocate the mdir below
lfs.mroot.rbyd.off = 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, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mdir to compact
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, NULL, 0) => 0;
// assert mdir was split correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot still has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[0 % 26], 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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.rbyd.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[0 % 26], 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// setup mroot to need to compact, this should trigger a relocation when
// we relocate the mdir below
lfs.mroot.rbyd.off = 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, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert mdir was dropped
assert(lfsr_mtree_weight(&lfs) == 0);
// assert mroot still has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[0 % 26], 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.rbyd.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[0 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// force mroot to compact once, so the second compact below will trigger
// a relocation
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact, this should trigger a relocation
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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.rbyd.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// force mroot to compact once, so the second compact below will trigger
// a relocation
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact, this should trigger a relocation
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
// assert that our entries are still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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.rbyd.weight == 0);
// assert we relocated our mroot
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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.SAMPLES = 500
# -1 => all pseudo-random seeds
# n => reproduce a specific seed
defines.SEED = -1
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = (SEED == -1 ? 1 : SEED);
(SEED == -1 ? seed < SAMPLES+1 : seed == SEED);
seed++) {
printf("--- seed: %d ---\n", seed);
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 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, mid, &mdir) => 0;
// choose a pseudo-random rid
lfs_ssize_t rid = TEST_PRNG(&prng) % (lfsr_mdir_weight(&mdir)+1);
// choose to create or delete
uint8_t op = (lfs_size_t)rid == lfsr_mdir_weight(&mdir)
? 0
: TEST_PRNG(&prng) % 3;
// force a compaction?
if (FORCE_COMPACTION) {
mdir.rbyd.off = cfg->block_size;
lfs.mroot.rbyd.off = cfg->block_size;
}
// create
if (op == 0) {
// add to rbyd
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, +1,
&alphas[i % 26], 1))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, 0,
&alphas[i % 26], 1))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
// delete
} else {
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, UNR, -1, NULL, 0))) => 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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid == -1 || mdir.rbyd.weight > 0);
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
// drop should make sure we never have empty mdirs
assert(mdir.mid == -1 || mdir.rbyd.weight > 0);
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1),
LFSR_ATTR(1, INLINED, +1, &alphas[1 % 26], 1))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot};
lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot};
lfsr_mdir_addopened(&lfs, &left_neighbor);
lfsr_mdir_addopened(&lfs, &right_neighbor);
// insert a new entry, this should update our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, &alphas[2 % 26], 1))) => 0;
// assert that our entry is still in the mtree
assert(lfs.mroot.rbyd.weight == 3);
uint8_t buffer[1];
lfsr_mdir_get(&lfs, &lfs.mroot, 1, LFSR_TAG_INLINED,
buffer, 1) => 1;
assert(memcmp(buffer, &alphas[2 % 26], 1) == 0);
// assert that our neighbors were updated correctly
assert(left_neighbor.rid == 0);
assert(left_neighbor.mdir.mid == -1);
assert(memcmp(&left_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0);
assert(right_neighbor.rid == 2);
assert(right_neighbor.mdir.mid == -1);
assert(memcmp(&right_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0);
lfsr_mdir_removeopened(&lfs, &left_neighbor);
lfsr_mdir_removeopened(&lfs, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_remove_l]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1),
LFSR_ATTR(1, INLINED, +1, &alphas[1 % 26], 1))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot};
lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot};
lfsr_mdir_addopened(&lfs, &left_neighbor);
lfsr_mdir_addopened(&lfs, &right_neighbor);
// try removing our left entry
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, UNR, -1, NULL, 0))) => 0;
// assert that an entry was removed
assert(lfs.mroot.rbyd.weight == 1);
// assert that our neighbors were updated correctly
assert(left_neighbor.rid == -2);
assert(left_neighbor.mdir.mid == -2);
assert(right_neighbor.rid == 0);
assert(right_neighbor.mdir.mid == -1);
assert(memcmp(&right_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0);
lfsr_mdir_removeopened(&lfs, &left_neighbor);
lfsr_mdir_removeopened(&lfs, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mtree_neighbor_remove_r]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1),
LFSR_ATTR(1, INLINED, +1, &alphas[1 % 26], 1))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot};
lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot};
lfsr_mdir_addopened(&lfs, &left_neighbor);
lfsr_mdir_addopened(&lfs, &right_neighbor);
// try removing our left entry
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, UNR, -1, NULL, 0))) => 0;
// assert that an entry was removed
assert(lfs.mroot.rbyd.weight == 1);
// assert that our neighbors were updated correctly
assert(left_neighbor.rid == 0);
assert(left_neighbor.mdir.mid == -1);
assert(memcmp(&left_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0);
assert(right_neighbor.rid == -2);
assert(right_neighbor.mdir.mid == -2);
lfsr_mdir_removeopened(&lfs, &left_neighbor);
lfsr_mdir_removeopened(&lfs, &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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1),
LFSR_ATTR(1, INLINED, +1, &alphas[1 % 26], 1))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot};
lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot};
lfsr_mdir_addopened(&lfs, &left_neighbor);
lfsr_mdir_addopened(&lfs, &right_neighbor);
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, alphas[3 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.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, &alphas[2 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 3);
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[3 % 26], 1) == 0);
// assert that our neighbors were updated correctly
assert(left_neighbor.rid == 0);
assert(left_neighbor.mdir.mid == 0);
assert(memcmp(&left_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0);
assert(right_neighbor.rid == 2);
assert(right_neighbor.mdir.mid == 0);
assert(memcmp(&right_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0);
lfsr_mdir_removeopened(&lfs, &left_neighbor);
lfsr_mdir_removeopened(&lfs, &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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1),
LFSR_ATTR(1, INLINED, +1, &alphas[1 % 26], 1))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot};
lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot};
lfsr_mdir_addopened(&lfs, &left_neighbor);
lfsr_mdir_addopened(&lfs, &right_neighbor);
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[3 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(2, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert that our entries are still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 2);
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[2 % 26], 1) == 0);
lfsr_mdir_t msibling;
lfsr_mtree_lookup(&lfs, 1, &msibling) => 0;
assert(msibling.rbyd.weight == 2);
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[3 % 26], 1) == 0);
// assert that our neighbors were updated correctly
assert(left_neighbor.rid == 0);
assert(left_neighbor.mdir.mid == 0);
assert(memcmp(&left_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0);
assert(right_neighbor.rid == 1);
assert(right_neighbor.mdir.mid == 1);
assert(memcmp(&right_neighbor.mdir, &msibling, sizeof(lfsr_mdir_t)) == 0);
lfsr_mdir_removeopened(&lfs, &left_neighbor);
lfsr_mdir_removeopened(&lfs, &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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1),
LFSR_ATTR(1, INLINED, +1, &alphas[1 % 26], 1))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot};
lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot};
lfsr_mdir_addopened(&lfs, &left_neighbor);
lfsr_mdir_addopened(&lfs, &right_neighbor);
// create an uninlined mdir
uint8_t buffer[SIZE];
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
memset(buffer, alphas[3 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// now add another large entry to the mdir, forcing a split
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 3);
memset(buffer, alphas[4 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){2}, LFSR_ATTRS(
LFSR_ATTR(2, INLINED, +1, buffer, SIZE))) => 0;
// force mdir to compact
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){2}, NULL, 0) => 0;
// assert mdir was split correctly
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot still has no entries
assert(lfs.mroot.rbyd.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, &alphas[2 % 26], 1) == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 2);
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[3 % 26], 1) == 0);
lfsr_mdir_t msibling;
lfsr_mtree_lookup(&lfs, 1, &msibling) => 0;
assert(msibling.rbyd.weight == 2);
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[4 % 26], 1) == 0);
// assert that our neighbors were updated correctly
assert(left_neighbor.rid == 0);
assert(left_neighbor.mdir.mid == 0);
assert(memcmp(&left_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0);
assert(right_neighbor.rid == 1);
assert(right_neighbor.mdir.mid == 1);
assert(memcmp(&right_neighbor.mdir, &msibling, sizeof(lfsr_mdir_t)) == 0);
lfsr_mdir_removeopened(&lfs, &left_neighbor);
lfsr_mdir_removeopened(&lfs, &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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1),
LFSR_ATTR(1, INLINED, +1, &alphas[1 % 26], 1))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot};
lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot};
lfsr_mdir_addopened(&lfs, &left_neighbor);
lfsr_mdir_addopened(&lfs, &right_neighbor);
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
lfs.mroot.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[1 % 26], 1) == 0);
// assert that our neighbors were updated correctly
assert(left_neighbor.rid == 0);
assert(left_neighbor.mdir.mid == -1);
assert(memcmp(&left_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0);
assert(right_neighbor.rid == 1);
assert(right_neighbor.mdir.mid == -1);
assert(memcmp(&right_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0);
lfsr_mdir_removeopened(&lfs, &left_neighbor);
lfsr_mdir_removeopened(&lfs, &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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// setup our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1),
LFSR_ATTR(1, INLINED, +1, &alphas[1 % 26], 1))) => 0;
// this test only works if these all fit in the mroot
assert(lfsr_mtree_isinlined(&lfs));
lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot};
lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot};
lfsr_mdir_addopened(&lfs, &left_neighbor);
lfsr_mdir_addopened(&lfs, &right_neighbor);
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, alphas[2 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, alphas[3 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mtree has one mdir
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// force mdir to compact twice, this should relocate
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 3);
lfsr_mdir_t old_mdir = mdir;
mdir.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0;
mdir.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[4 % 26], SIZE);
lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 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, &alphas[2 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 3);
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[4 % 26], 1) == 0);
// assert that our neighbors were updated correctly
assert(left_neighbor.rid == 0);
assert(left_neighbor.mdir.mid == 0);
assert(memcmp(&left_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0);
assert(right_neighbor.rid == 2);
assert(right_neighbor.mdir.mid == 0);
assert(memcmp(&right_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0);
lfsr_mdir_removeopened(&lfs, &left_neighbor);
lfsr_mdir_removeopened(&lfs, &right_neighbor);
lfsr_unmount(&lfs) => 0;
'''
## mtree traversal ##
# test specific corner cases
[cases.test_mtree_traversal]
defines.VALIDATE = [false, true]
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// insert a new entry, this should update our neighbors
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, &alphas[0 % 26], 1))) => 0;
// assert that our entry is still in the mtree
assert(lfs.mroot.rbyd.weight == 1);
uint8_t buffer[1];
lfsr_mdir_get(&lfs, &lfs.mroot, 0, LFSR_TAG_INLINED,
buffer, 1) => 1;
assert(memcmp(buffer, &alphas[0 % 26], 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_INIT(
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);
lfs_size_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_.buf.buffer;
printf("traversal: %d 0x%x btree 0x%x.%x\n",
mid_,
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_.buf.buffer;
printf("traversal: %d 0x%x mdir 0x{%x,%x}\n",
mid_,
tag_,
mdir->rbyd.block, mdir->redund_block);
// keep track of seen blocks
seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8);
seen[mdir->redund_block / 8] |= 1 << (mdir->redund_block % 8);
} else {
// this shouldn't happen
printf("traversal: %d 0x%x %d\n",
mid_,
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.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &lfs.mroot, 0, LFSR_TAG_INLINED,
buffer, 1) => 1;
assert(memcmp(buffer, &alphas[0 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with a large attr so the next entry can not fit
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// create a large entry that needs to be uninlined (but not split!)
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdir was unininlined correctly
assert(lfsr_mtree_weight(&lfs) == 1);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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_INIT(
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);
lfs_size_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_.buf.buffer;
printf("traversal: %d 0x%x btree 0x%x.%x\n",
mid_,
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_.buf.buffer;
printf("traversal: %d 0x%x mdir 0x{%x,%x}\n",
mid_,
tag_,
mdir->rbyd.block, mdir->redund_block);
// keep track of seen blocks
seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8);
seen[mdir->redund_block / 8] |= 1 << (mdir->redund_block % 8);
} else {
// this shouldn't happen
printf("traversal: %d 0x%x %d\n",
mid_,
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.rbyd.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, &alphas[0 % 26], 1) == 0);
// assert that our entry is still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// create 2 large entries that needs to be uninlined and split
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(0, INLINED, +1, buffer, SIZE))) => 0;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(1, INLINED, +1, buffer, SIZE))) => 0;
// force mroot to compact
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
// assert mdirs were unininlined and split
assert(lfsr_mtree_weight(&lfs) == 2);
// assert mroot now has no entries
assert(lfs.mroot.rbyd.weight == 0);
// assert that our entries are still in the mtree
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 1) == 0);
lfsr_mdir_t msibling;
lfsr_mtree_lookup(&lfs, 1, &msibling) => 0;
assert(msibling.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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_INIT(
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);
lfs_size_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_.buf.buffer;
printf("traversal: %d 0x%x btree 0x%x.%x\n",
mid_,
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_.buf.buffer;
printf("traversal: %d 0x%x mdir 0x{%x,%x}\n",
mid_,
tag_,
mdir->rbyd.block, mdir->redund_block);
// keep track of seen blocks
seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8);
seen[mdir->redund_block / 8] |= 1 << (mdir->redund_block % 8);
} else {
// this shouldn't happen
printf("traversal: %d 0x%x %d\n",
mid_,
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.rbyd.weight == 0);
// assert that our entries are still in the mtree
lfsr_mtree_lookup(&lfs, 0, &mdir) => 0;
assert(mdir.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[0 % 26], 1) == 0);
lfsr_mtree_lookup(&lfs, 1, &msibling) => 0;
assert(msibling.rbyd.weight == 1);
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_INLINED,
buffer, SIZE) => SIZE;
assert(memcmp(buffer, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
lfs.mroot.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[1 % 26], 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_INIT(
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);
lfs_size_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_.buf.buffer;
printf("traversal: %d 0x%x btree 0x%x.%x\n",
mid_,
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_.buf.buffer;
printf("traversal: %d 0x%x mdir 0x{%x,%x}\n",
mid_,
tag_,
mdir->rbyd.block, mdir->redund_block);
// keep track of seen blocks
seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8);
seen[mdir->redund_block / 8] |= 1 << (mdir->redund_block % 8);
} else {
// this shouldn't happen
printf("traversal: %d 0x%x %d\n",
mid_,
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, &lfs.mroot) != 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, &alphas[1 % 26], 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;
// create entries
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, lfsr_mtree_weight(&lfs)-1, &mdir) => 0;
lfs_ssize_t rid = 0;
for (lfs_size_t i = 0; i < N; i++) {
// force a compaction?
if (FORCE_COMPACTION) {
mdir.rbyd.off = cfg->block_size;
lfs.mroot.rbyd.off = cfg->block_size;
}
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, +1, &alphas[i % 26], 1))) => 0;
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
rid += 1;
}
// try looking up each entry
lfs_size_t i = 0;
for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
mid < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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_INIT(
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));
lfs_size_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_.buf.buffer;
printf("traversal: %d 0x%x btree 0x%x.%x\n",
mid_,
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_.buf.buffer;
printf("traversal: %d 0x%x mdir 0x{%x,%x}\n",
mid_,
tag_,
mdir->rbyd.block, mdir->redund_block);
// keep track of seen blocks
seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8);
seen[mdir->redund_block / 8] |= 1 << (mdir->redund_block % 8);
} else {
// this shouldn't happen
printf("traversal: %d 0x%x %d\n",
mid_,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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.SAMPLES = 100
# -1 => all pseudo-random seeds
# n => reproduce a specific seed
defines.SEED = -1
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = (SEED == -1 ? 1 : SEED);
(SEED == -1 ? seed < SAMPLES+1 : seed == SEED);
seed++) {
printf("--- seed: %d ---\n", seed);
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 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, mid, &mdir) => 0;
// choose a pseudo-random rid
lfs_ssize_t rid = TEST_PRNG(&prng) % (lfsr_mdir_weight(&mdir)+1);
// force a compaction?
if (FORCE_COMPACTION) {
mdir.rbyd.off = cfg->block_size;
lfs.mroot.rbyd.off = cfg->block_size;
}
// add to rbyd, potentially splitting the mdir
lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, INLINED, +1, &alphas[i % 26], 1))) => 0;
// make sure we can look up the new entry
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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_INIT(
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));
lfs_size_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_.buf.buffer;
printf("traversal: %d 0x%x btree 0x%x.%x\n",
mid_,
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_.buf.buffer;
printf("traversal: %d 0x%x mdir 0x{%x,%x}\n",
mid_,
tag_,
mdir->rbyd.block, mdir->redund_block);
// keep track of seen blocks
seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8);
seen[mdir->redund_block / 8] |= 1 << (mdir->redund_block % 8);
} else {
// this shouldn't happen
printf("traversal: %d 0x%x %d\n",
mid_,
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 < lfsr_mtree_weight(&lfs);
mid++) {
lfsr_mdir_t mdir;
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
for (lfs_ssize_t rid = 0;
rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir);
rid++) {
uint8_t buffer[4];
lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED,
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;
uint8_t buffer[LFSR_MPTR_DSIZE];
lfs_ssize_t d = lfsr_mptr_todisk(&lfs, LFSR_MPTR_MROOTANCHOR, buffer);
assert(d >= 0);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, MROOT, 0, buffer, d))) => 0;
// technically, cycle detection only needs to work when we're validating
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL_INIT(
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);
lfs_size_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_.buf.buffer;
printf("traversal: %d 0x%x btree 0x%x.%x\n",
mid_,
tag_,
branch->block, branch->trunk);
} else if (tag_ == LFSR_TAG_MDIR) {
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.buf.buffer;
printf("traversal: %d 0x%x mdir 0x{%x,%x}\n",
mid_,
tag_,
mdir->rbyd.block, mdir->redund_block);
} else {
// this shouldn't happen
printf("traversal: %d 0x%x %d\n",
mid_,
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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// force mroot to compact twice, this should extend the mroot
lfsr_mdir_t old_mroot = lfs.mroot;
lfs.mroot.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
lfs.mroot.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[1 % 26], 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 = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// prepare mroot with an attr
uint8_t buffer[SIZE];
memset(buffer, alphas[0 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, 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.rbyd.off = BLOCK_SIZE;
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
}
lfs.mroot.rbyd.off = BLOCK_SIZE;
memset(buffer, alphas[1 % 26], SIZE);
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
// assert we relocated
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, &alphas[1 % 26], 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);
'''