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]