Added explicit tests over btree reinlining

These tests, and this feature really, is a bit tricky since our btrees
reinline "lazily". That is, our btrees only check if they can inline
during compaction, allowing potentially inlinable btrees to remain
uninlined.

This better utilizes any erased storage in the btree's rbyd, but adds
some corner cases we need to be concerned about.

Added because of some ongoing btree rewrite work, where it did catch
incorrect behavior.
This commit is contained in:
Christopher Haster
2023-08-18 22:27:22 -05:00
parent 5541ad0c00
commit 3dbc986752
+257
View File
@@ -2425,6 +2425,263 @@ code = '''
'''
# test we reinline (go from uninlined to inlined) correctly, this is a bit
# tricky since our btrees lazily reinline
[cases.test_btree_reinline_pop_set]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfs_init(&lfs, CFG) => 0;
// create free lookahead
memset(lfs.lookahead.buffer, 0, CFG->lookahead_size);
lfs.lookahead.start = 0;
lfs.lookahead.size = lfs_min(8*CFG->lookahead_size,
CFG->block_count);
lfs.lookahead.next = 0;
lfs_alloc_ack(&lfs);
// create an uninlined tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1,
LFSR_DATA("b", 1)) => 0;
assert(lfsr_btree_weight(&btree) == 2);
assert(!lfsr_btree_isinlined(&btree));
// pop! our btree should now be reinlinable
lfsr_btree_pop(&lfs, &btree, 0) => 0;
// but thanks to lazy reinlining, our btree won't reinline until
// it is compacted, so we need to add commits until it is compacted
lfs_block_t before_block = btree.u.r.rbyd.block;
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < BLOCK_SIZE);
// commit to btree
lfsr_btree_set(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("b", 1)) => 0;
assert(lfsr_btree_weight(&btree) == 1);
// try looking up tag to hopefully catch if something breaks
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
// inlined? consider this a success
if (lfsr_btree_isinlined(&btree)) {
break;
}
// assert if a compaction occurred that wasn't inlined
assert(btree.u.r.rbyd.block == before_block);
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
'''
[cases.test_btree_reinline_pop_pop_push]
in = 'lfs.c'
defines.SHIFT = 'range(5)'
code = '''
lfs_t lfs;
lfs_init(&lfs, CFG) => 0;
// create free lookahead
memset(lfs.lookahead.buffer, 0, CFG->lookahead_size);
lfs.lookahead.start = 0;
lfs.lookahead.size = lfs_min(8*CFG->lookahead_size,
CFG->block_count);
lfs.lookahead.next = 0;
lfs_alloc_ack(&lfs);
// create an uninlined tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1,
LFSR_DATA("b", 1)) => 0;
assert(lfsr_btree_weight(&btree) == 2);
assert(!lfsr_btree_isinlined(&btree));
// pop! our btree should now be reinlinable
lfsr_btree_pop(&lfs, &btree, 0) => 0;
// It's difficult to test reinlining during push or pop, since we can't just
// repeat the action until compaction occurs.
//
// What we do here is alternate between 0 and 1 entries, eventually we
// will compact during one of either a push or pop. To try to cover both,
// test with some number of extra commits to hopefully adjust where the
// compaction ends up.
for (lfs_size_t i = 0; i < SHIFT; i++) {
lfsr_btree_set(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("b", 1)) => 0;
}
// alternate between push/pop until compaction occurs
lfs_block_t before_block = btree.u.r.rbyd.block;
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < BLOCK_SIZE);
// pop!
lfsr_btree_pop(&lfs, &btree, 0) => 0;
assert(lfsr_btree_weight(&btree) == 0);
// inlined? consider this a success
if (lfsr_btree_isinlined(&btree)) {
break;
}
// assert if a compaction occurred that wasn't inlined
assert(btree.u.r.rbyd.block == before_block);
// push!
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("c", 1)) => 0;
// try looking up tag to hopefully catch if something breaks
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "c", 1) == 0);
// inlined? consider this a success
if (lfsr_btree_isinlined(&btree)) {
break;
}
// assert if a compaction occurred that wasn't inlined
assert(btree.u.r.rbyd.block == before_block);
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
'''
[cases.test_btree_reinline_pop_push]
in = 'lfs.c'
defines.SIBLING = [0, 1]
defines.SHIFT = 'range(5)'
code = '''
lfs_t lfs;
lfs_init(&lfs, CFG) => 0;
// create free lookahead
memset(lfs.lookahead.buffer, 0, CFG->lookahead_size);
lfs.lookahead.start = 0;
lfs.lookahead.size = lfs_min(8*CFG->lookahead_size,
CFG->block_count);
lfs.lookahead.next = 0;
lfs_alloc_ack(&lfs);
// create an uninlined tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1,
LFSR_DATA("b", 1)) => 0;
assert(lfsr_btree_weight(&btree) == 2);
assert(!lfsr_btree_isinlined(&btree));
// It's difficult to test reinlining during push or pop, since we can't just
// repeat the action until compaction occurs.
//
// Here we alternate between 1 and 2 entries, with the hope that compaction
// occurs on the pop. We try this with some number of extra commits to make
// it more likely pop is tested.
//
// It's possible our commits line up so compaction always occurs on a push!
// For this reason, we end the test if an non-reinlining compaction occurs.
for (lfs_size_t i = 0; i < SHIFT; i++) {
lfsr_btree_set(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
}
// alternate between push/pop until compaction occurs
lfs_block_t before_block = btree.u.r.rbyd.block;
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < BLOCK_SIZE);
// pop!
lfsr_btree_pop(&lfs, &btree, SIBLING) => 0;
assert(lfsr_btree_weight(&btree) == 1);
// try looking up tag to hopefully catch if something breaks
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, (SIBLING == 1 ? "a" : "b"), 1) == 0);
// inlined? consider this a success
if (lfsr_btree_isinlined(&btree)) {
break;
}
// abort if a compaction occurs
if (btree.u.r.rbyd.block != before_block) {
break;
}
// push!
lfsr_btree_push(&lfs, &btree, SIBLING, LFSR_TAG_INLINED, 1,
LFSR_DATA("c", 1)) => 0;
// try looking up tag to hopefully catch if something breaks
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, (SIBLING == 1 ? "a" : "c"), 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, (SIBLING == 1 ? "c" : "b"), 1) == 0);
// inlined? consider this a success
if (lfsr_btree_isinlined(&btree)) {
break;
}
// abort if a compaction occurs
if (btree.u.r.rbyd.block != before_block) {
break;
}
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
'''
# Some more general fuzz testing
[cases.test_btree_general_fuzz]