From 3dbc986752c3e32e11a2d0b766d6f24bf61afb16 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 18 Aug 2023 22:27:22 -0500 Subject: [PATCH] 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. --- tests/test_btree.toml | 257 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) diff --git a/tests/test_btree.toml b/tests/test_btree.toml index 2636556e..7b8b172f 100644 --- a/tests/test_btree.toml +++ b/tests/test_btree.toml @@ -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]