Files
littlefs/tests/test_btree.toml
T
Christopher Haster f5436caf24 Extended appendall to adjust bid-relative attrs, made attr-lists const again
This adds an extra bid parameter to lfsr_rbyd_appendall so that attrs
relative to a bid can be adjusted correctly.

This allows us to make attr-lists const again, which is generally a good
things. Passing around complex mutable state is just asking for bugs.

Though since these attr-lists are generally just passed as temporary
arguments, maybe it's not that bad?
2023-08-19 12:10:17 -05:00

4367 lines
134 KiB
TOML

# Test the mid-level B-trees
after = 'test_rbyd'
# maximize lookahead buffer, we don't actually gc so we only get one pass
# of the disk for these tests
defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)'
# test an empty tree
[cases.test_btree_zero]
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 empty tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 0);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
# test an inlined tree
[cases.test_btree_one]
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 a single-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 1);
// try looking up tags
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, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
# test a single-rbyd tree
[cases.test_btree_two]
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 a two-entry 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;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 2);
// try looking up tags
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, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_two_backwards]
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 a two-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("b", 1)) => 0;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 2);
// try looking up tags
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, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
# still a single-rbyd tree, just making sure it works
[cases.test_btree_three]
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 a two-entry 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;
lfsr_btree_push(&lfs, &btree, 2, LFSR_TAG_INLINED, 1,
LFSR_DATA("c", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 3);
// try looking up tags
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, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "c", 1) == 0);
lfsr_btree_get(&lfs, &btree, 3,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_three_backwards]
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 a two-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("c", 1)) => 0;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("b", 1)) => 0;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 3);
// try looking up tags
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, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "c", 1) == 0);
lfsr_btree_get(&lfs, &btree, 3,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
# try larger trees, when exactly a tree splits depends on the disk geometry, so
# we don't really have a better way of testing multi-rbyd trees
[cases.test_btree_push]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_size_t n = 0;
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
n += 1;
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == n);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < n; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, n,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_push_backwards]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_size_t n = 0;
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[(N-1-i) % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
n += 1;
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == n);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < n; i++) {
lfsr_btree_get(&lfs, &btree, n-1-i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[(N-1-i) % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, n,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_push_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t sim_size = 0;
memset(sim, 0, N);
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1);
// add to btree
int err = lfsr_btree_push(&lfs, &btree, bid, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// add to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
sim[bid] = alphas[i % 26];
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, sim_size,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// clean up sim
free(sim);
lfs_deinit(&lfs) => 0;
'''
[cases.test_btree_push_sparse]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.W = 5
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_size_t n = 0;
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i*W, LFSR_TAG_INLINED, W,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
n += 1;
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == n*W);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < n; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, n*W,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
lfs_size_t bid_ = -1;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < n; i++) {
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == i*W+W-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
'''
[cases.test_btree_push_sparse_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.W = 5
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t *sim_weights = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 0;
memset(sim, 0, N);
memset(sim_weights, 0, N*sizeof(lfs_size_t));
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1);
// choose a pseudo-random weight
lfs_size_t weight = 1 + (TEST_PRNG(&prng) % W);
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < bid; j++) {
weighted_bid += sim_weights[j];
}
// add to btree
int err = lfsr_btree_push(&lfs, &btree,
weighted_bid, LFSR_TAG_INLINED, weight,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// add to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
memmove(&sim_weights[bid+1], &sim_weights[bid],
(sim_size-bid)*sizeof(lfs_size_t));
sim[bid] = alphas[i % 26];
sim_weights[bid] = weight;
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%dw%d=%c", weighted_bid+sim_weights[i]-1,
sim_weights[i], sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
total_weight += sim_weights[j];
}
assert(lfsr_btree_weight(&btree) == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_bid+sim_weights[i]-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, total_weight,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
lfs_size_t bid_ = -1;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == weighted_bid+sim_weights[i]-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
// clean up sim
free(sim);
'''
# test btree updates
# try some small trees for easy corner cases first
[cases.test_btree_update_one]
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 a single-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
// update the tree
lfsr_btree_set(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("A", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 1);
// try looking up tags
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, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_update_two]
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 a two-entry 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;
// update the tree
lfsr_btree_set(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("A", 1)) => 0;
lfsr_btree_set(&lfs, &btree, 1, LFSR_TAG_INLINED, 1,
LFSR_DATA("B", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 2);
// try looking up tags
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, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "B", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_update_three]
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 a two-entry 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;
lfsr_btree_push(&lfs, &btree, 2, LFSR_TAG_INLINED, 1,
LFSR_DATA("c", 1)) => 0;
// update the tree
lfsr_btree_set(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("A", 1)) => 0;
lfsr_btree_set(&lfs, &btree, 1, LFSR_TAG_INLINED, 1,
LFSR_DATA("B", 1)) => 0;
lfsr_btree_set(&lfs, &btree, 2, LFSR_TAG_INLINED, 1,
LFSR_DATA("C", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 3);
// try looking up tags
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, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "B", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "C", 1) == 0);
lfsr_btree_get(&lfs, &btree, 3,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_update]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
// update the tree
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_set(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
LFSR_DATA(&uppers[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == N);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &uppers[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, N,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_update_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.SAMPLES = 10
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
for (lfs_size_t i = 0; i < N; i++) {
sim[i] = alphas[i % 26];
}
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % N;
// update btree
int err = lfsr_btree_set(&lfs, &btree, bid, LFSR_TAG_INLINED, 1,
LFSR_DATA(&uppers[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// update sim
sim[bid] = uppers[i % 26];
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < N; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == N);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, N,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// clean up sim
free(sim);
'''
[cases.test_btree_update_sparse]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.W = 5
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i*W, LFSR_TAG_INLINED, W,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
// update the tree
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_set(&lfs, &btree, i*W+W-1, LFSR_TAG_INLINED, W,
LFSR_DATA(&uppers[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == N*W);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
assert(memcmp(buffer, &uppers[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, N*W,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
lfs_size_t bid_ = -1;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == i*W+W-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &uppers[i % 26], 1) == 0);
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
'''
[cases.test_btree_update_sparse_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.W = 5
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i*W, LFSR_TAG_INLINED, W,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t *sim_weights = malloc(N*sizeof(lfs_size_t));
for (lfs_size_t i = 0; i < N; i++) {
sim[i] = alphas[i % 26];
sim_weights[i] = W;
}
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % N;
// choose a pseudo-random weight
lfs_size_t weight = 1 + (TEST_PRNG(&prng) % W);
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < bid; j++) {
weighted_bid += sim_weights[j];
}
// update btree
int err = lfsr_btree_set(&lfs, &btree,
weighted_bid+sim_weights[bid]-1, LFSR_TAG_INLINED, weight,
LFSR_DATA(&uppers[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// update sim
sim[bid] = uppers[i % 26];
sim_weights[bid] = weight;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%dw%d=%c", weighted_bid+sim_weights[i]-1,
sim_weights[i], sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < N; j++) {
total_weight += sim_weights[j];
}
assert(lfsr_btree_weight(&btree) == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_bid+sim_weights[i]-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, total_weight,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
lfs_size_t bid_ = -1;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == weighted_bid+sim_weights[i]-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
// clean up sim
free(sim);
'''
# test btree pops
# try some corner cases first, these are actually pretty tricky since we
# need to recognize when to collapse back into an inlined tree
[cases.test_btree_pop_one]
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 a single-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("a", 1)) => 0;
// pop!
lfsr_btree_pop(&lfs, &btree, 0) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 0);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// try to putting it back to see if things still work
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("A", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 1);
// try looking up tags
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_pop_two]
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 a single-entry 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;
// pop!
lfsr_btree_pop(&lfs, &btree, 1) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 1);
// try looking up tags
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, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// try to putting it back to see if things still work
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1,
LFSR_DATA("B", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 2);
// try looking up tags
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "B", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_pop_two_other]
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 a single-entry 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;
// pop!
lfsr_btree_pop(&lfs, &btree, 0) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 1);
// try looking up tags
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);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// try to putting it back to see if things still work
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("A", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 2);
// try looking up tags
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_pop_three]
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 a single-entry 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;
lfsr_btree_push(&lfs, &btree, 2, LFSR_TAG_INLINED, 1,
LFSR_DATA("c", 1)) => 0;
// pop!
lfsr_btree_pop(&lfs, &btree, 2) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 2);
// try looking up tags
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, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// try to putting it back to see if things still work
lfsr_btree_push(&lfs, &btree, 2, LFSR_TAG_INLINED, 1,
LFSR_DATA("C", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 3);
// try looking up tags
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "C", 1) == 0);
lfsr_btree_get(&lfs, &btree, 3,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_pop]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.REMAINING = [64, 2, 1, 0]
if = 'N > REMAINING'
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
// drain the tree
for (lfs_size_t i = 0; i < N-REMAINING; i++) {
int err = lfsr_btree_pop(&lfs, &btree, N-1-i);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == REMAINING);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, REMAINING,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// try recovering
lfsr_btree_push(&lfs, &btree, REMAINING, LFSR_TAG_INLINED, 1,
LFSR_DATA("R", 1)) => 0;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, REMAINING,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "R", 1) == 0);
lfsr_btree_get(&lfs, &btree, REMAINING+1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_pop_backwards]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.REMAINING = [64, 2, 1, 0]
if = 'N > REMAINING'
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
// drain the tree
for (lfs_size_t i = 0; i < N-REMAINING; i++) {
int err = lfsr_btree_pop(&lfs, &btree, 0);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == REMAINING);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[(i+(N-REMAINING)) % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, REMAINING,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// try recovering
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("R", 1)) => 0;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, "R", 1) == 0);
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i+1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[(i+(N-REMAINING)) % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, REMAINING+1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_pop_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.REMAINING = [64, 2, 1, 0]
defines.SEED = 'range(10)'
if = 'N > REMAINING'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t sim_size = N;
for (lfs_size_t i = 0; i < N; i++) {
sim[i] = alphas[i % 26];
}
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < (N-REMAINING); i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % sim_size;
// remove from btree
int err = lfsr_btree_pop(&lfs, &btree, bid);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// remove from sim
memmove(&sim[bid], &sim[bid+1], sim_size-(bid+1));
sim_size -= 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, sim_size,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// clean up sim
free(sim);
'''
[cases.test_btree_pop_sparse]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.W = 5
defines.REMAINING = [64, 2, 1, 0]
if = 'N > REMAINING'
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i*W, LFSR_TAG_INLINED, W,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
// drain the tree
for (lfs_size_t i = 0; i < N-REMAINING; i++) {
int err = lfsr_btree_pop(&lfs, &btree, (N-1-i)*W+W-1);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == REMAINING*W);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, REMAINING*W+W-1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// try recovering
lfsr_btree_push(&lfs, &btree, REMAINING*W, LFSR_TAG_INLINED, W,
LFSR_DATA("R", 1)) => 0;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, REMAINING*W+W-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
assert(memcmp(buffer, "R", 1) == 0);
lfsr_btree_get(&lfs, &btree, (REMAINING+1)*W+W-1,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
lfs_size_t bid_ = -1;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == i*W+W-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == REMAINING*W+W-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "R", 1) == 0);
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
'''
[cases.test_btree_pop_sparse_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.W = 5
defines.REMAINING = [64, 2, 1, 0]
defines.SEED = 'range(10)'
if = 'N > REMAINING'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t *sim_weights = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 0;
// set up simulation and btree with pseudo-random weights
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random weight
lfs_size_t weight = 1 + (TEST_PRNG(&prng) % W);
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
int err = lfsr_btree_push(&lfs, &btree,
weighted_bid, LFSR_TAG_INLINED, weight,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
return;
}
assert(err == 0);
sim[i] = alphas[i % 26];
sim_weights[i] = weight;
sim_size += 1;
}
for (lfs_size_t i = 0; i < (N-REMAINING); i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % sim_size;
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < bid; j++) {
weighted_bid += sim_weights[j];
}
// remove from btree
int err = lfsr_btree_pop(&lfs, &btree,
weighted_bid+sim_weights[bid]-1);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// remove from sim
memmove(&sim[bid], &sim[bid+1], sim_size-(bid+1));
memmove(&sim_weights[bid], &sim_weights[bid+1],
(sim_size-(bid+1))*sizeof(lfs_size_t));
sim_size -= 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%dw%d=%c", weighted_bid+sim_weights[i]-1,
sim_weights[i], sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
total_weight += sim_weights[j];
}
assert(lfsr_btree_weight(&btree) == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_bid+sim_weights[i]-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, total_weight,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
lfs_size_t bid_ = -1;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == weighted_bid+sim_weights[i]-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
// clean up sim
free(sim);
'''
# test btree splits
[cases.test_btree_split]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[0 % 26], 1)) => 0;
lfs_size_t n = 1;
for (lfs_size_t i = 1; i < N; i++) {
int err = lfsr_btree_split(&lfs, &btree, i-1, LFSR_DATA_NULL,
LFSR_TAG_INLINED, 1, LFSR_DATA(&alphas[(i-1) % 26], 1),
LFSR_TAG_INLINED, 1, LFSR_DATA(&alphas[(i-0) % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
n += 1;
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == n);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < n; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, n,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_split_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("_", 1)) => 0;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t sim_size = 1;
memset(sim, 0, N);
sim[0] = '_';
uint32_t prng = SEED;
for (lfs_size_t i = 1; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % sim_size;
// split btree
int err = lfsr_btree_split(&lfs, &btree, bid, LFSR_DATA_NULL,
LFSR_TAG_INLINED, 1, LFSR_DATA(&alphas[i % 26], 1),
LFSR_TAG_INLINED, 1, LFSR_DATA(&uppers[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// split sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
sim[bid+0] = alphas[i % 26];
sim[bid+1] = uppers[i % 26];
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, sim_size,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// clean up sim
free(sim);
lfs_deinit(&lfs) => 0;
'''
[cases.test_btree_split_sparse]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.W = 5
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, W,
LFSR_DATA(&alphas[0 % 26], 1)) => 0;
lfs_size_t n = 1;
for (lfs_size_t i = 1; i < N; i++) {
int err = lfsr_btree_split(&lfs, &btree, (i-1)*W+W-1, LFSR_DATA_NULL,
LFSR_TAG_INLINED, W, LFSR_DATA(&alphas[(i-1) % 26], 1),
LFSR_TAG_INLINED, W, LFSR_DATA(&alphas[(i-0) % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
n += 1;
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == n*W);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < n; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == W);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, n*W,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_split_sparse_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.W = 5
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, W,
LFSR_DATA("_", 1)) => 0;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t *sim_weights = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 1;
memset(sim, 0, N);
memset(sim_weights, 0, N*sizeof(lfs_size_t));
sim[0] = '_';
sim_weights[0] = W;
uint32_t prng = SEED;
for (lfs_size_t i = 1; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % sim_size;
// choose pseudo-random weights
lfs_size_t weight1 = 1 + (TEST_PRNG(&prng) % W);
lfs_size_t weight2 = 1 + (TEST_PRNG(&prng) % W);
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < bid; j++) {
weighted_bid += sim_weights[j];
}
// split btree
int err = lfsr_btree_split(&lfs, &btree,
weighted_bid+sim_weights[bid]-1, LFSR_DATA_NULL,
LFSR_TAG_INLINED, weight1,
LFSR_DATA(&alphas[i % 26], 1),
LFSR_TAG_INLINED, weight2,
LFSR_DATA(&uppers[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// add to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
memmove(&sim_weights[bid+1], &sim_weights[bid],
(sim_size-bid)*sizeof(lfs_size_t));
sim[bid+0] = alphas[i % 26];
sim[bid+1] = uppers[i % 26];
sim_weights[bid+0] = weight1;
sim_weights[bid+1] = weight2;
sim_size += 1;
// TODO rm
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
total_weight += sim_weights[j];
}
assert(lfsr_btree_weight(&btree) == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_bid+sim_weights[i]-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%dw%d=%c", weighted_bid+sim_weights[i]-1,
sim_weights[i], sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
total_weight += sim_weights[j];
}
assert(lfsr_btree_weight(&btree) == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_bid+sim_weights[i]-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, total_weight,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
lfs_size_t bid_ = -1;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == weighted_bid+sim_weights[i]-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
// clean up sim
free(sim);
'''
# TODO
## 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]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.SEED = 'range(100)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t sim_size = 0;
memset(sim, 0, N);
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random op
uint8_t op = TEST_PRNG(&prng) % 3;
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1);
if (op == 0 || bid == sim_size) {
// push to btree
int err = lfsr_btree_push(&lfs, &btree, bid,
LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// push to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
sim[bid] = alphas[i % 26];
sim_size += 1;
} else if (op == 1) {
// update btree
int err = lfsr_btree_set(&lfs, &btree, bid,
LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// update sim
sim[bid] = alphas[i % 26];
} else {
// pop from btree
int err = lfsr_btree_pop(&lfs, &btree, bid);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// pop from sim
memmove(&sim[bid], &sim[bid+1], sim_size-(bid+1));
sim_size -= 1;
}
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, sim_size,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// clean up sim
free(sim);
'''
[cases.test_btree_general_sparse_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.W = 5
defines.SEED = 'range(100)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t *sim_weights = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 0;
memset(sim, 0, N);
memset(sim_weights, 0, N*sizeof(lfs_size_t));
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random op
uint8_t op = TEST_PRNG(&prng) % 3;
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1);
// choose a pseudo-random weight
lfs_size_t weight = 1 + (TEST_PRNG(&prng) % W);
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < bid; j++) {
weighted_bid += sim_weights[j];
}
if (op == 0 || bid == sim_size) {
// push to btree
int err = lfsr_btree_push(&lfs, &btree, weighted_bid,
LFSR_TAG_INLINED, weight,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// push to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
memmove(&sim_weights[bid+1], &sim_weights[bid],
(sim_size-bid)*sizeof(lfs_size_t));
sim[bid] = alphas[i % 26];
sim_weights[bid] = weight;
sim_size += 1;
} else if (op == 1) {
// update btree
int err = lfsr_btree_set(&lfs, &btree,
weighted_bid+sim_weights[bid]-1, LFSR_TAG_INLINED, weight,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// update sim
sim[bid] = alphas[i % 26];
sim_weights[bid] = weight;
} else {
// remove from btree
int err = lfsr_btree_pop(&lfs, &btree,
weighted_bid+sim_weights[bid]-1);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// remove from sim
memmove(&sim[bid], &sim[bid+1], sim_size-(bid+1));
memmove(&sim_weights[bid], &sim_weights[bid+1],
(sim_size-(bid+1))*sizeof(lfs_size_t));
sim_size -= 1;
}
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%dw%d=%c", weighted_bid+sim_weights[i]-1,
sim_weights[i], sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
total_weight += sim_weights[j];
}
assert(lfsr_btree_weight(&btree) == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_bid+sim_weights[i]-1,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, total_weight,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
lfs_size_t bid_ = -1;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => 0;
assert(bid_ == weighted_bid+sim_weights[i]-1);
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == sim_weights[i]);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
lfsr_btree_lookupnext(&lfs, &btree, bid_+1,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
// clean up sim
free(sim);
'''
# test key-value btrees
[cases.test_btree_find_zero]
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 a zero-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 0);
// try to find tags
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
lfsr_btree_namelookup(&lfs, &btree, 0, "aaa", 3,
&bid_, &tag_, &weight_, &data_) => LFS_ERR_NOENT;
'''
[cases.test_btree_find_one]
# true or false for if we should use dids vs names
defines.DID = [false, true]
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 a single-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("0", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 1);
// try to find tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
lfsr_btree_namelookup(&lfs, &btree, 0*DID, "aaa", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 0);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "0", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 1*DID, "aab", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 0);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "0", 1) == 0);
'''
[cases.test_btree_find_two]
# true or false for if we should use dids vs names
defines.DID = [false, true]
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 a two-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("0", 1)) => 0;
lfsr_btree_split(&lfs, &btree, 0, LFSR_DATA_NAME(0*DID, "aab", 3),
LFSR_TAG_INLINED, 1, LFSR_DATA("0", 1),
LFSR_TAG_INLINED, 1, LFSR_DATA("1", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 2);
// try to find tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
lfsr_btree_namelookup(&lfs, &btree, 0, "aaa", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 0);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "0", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 0, "aab", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 1);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "1", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 0, "aac", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 1);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "1", 1) == 0);
'''
[cases.test_btree_find_three]
in = 'lfs.c'
# true or false for if we should use dids vs names
defines.DID = [false, true]
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 a two-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("0", 1)) => 0;
lfsr_btree_split(&lfs, &btree, 0, LFSR_DATA_NAME(1*DID, "aab", 3),
LFSR_TAG_INLINED, 1, LFSR_DATA("0", 1),
LFSR_TAG_INLINED, 1, LFSR_DATA("1", 1)) => 0;
lfsr_btree_split(&lfs, &btree, 1, LFSR_DATA_NAME(2*DID, "aac", 3),
LFSR_TAG_INLINED, 1, LFSR_DATA("1", 1),
LFSR_TAG_INLINED, 1, LFSR_DATA("2", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 3);
// try to find tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
lfsr_btree_namelookup(&lfs, &btree, 0*DID, "aaa", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 0);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "0", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 1*DID, "aab", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 1);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "1", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 2*DID, "aac", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 2);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "2", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 3*DID, "aad", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 2);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "2", 1) == 0);
'''
[cases.test_btree_find_three_backwards]
# true or false for if we should use dids vs names
defines.DID = [false, true]
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 a two-entry tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("0", 1)) => 0;
lfsr_btree_split(&lfs, &btree, 0, LFSR_DATA_NAME(2*DID, "aac", 3),
LFSR_TAG_INLINED, 1, LFSR_DATA("1", 1),
LFSR_TAG_INLINED, 1, LFSR_DATA("2", 1)) => 0;
lfsr_btree_split(&lfs, &btree, 0, LFSR_DATA_NAME(1*DID, "aab", 3),
LFSR_TAG_INLINED, 1, LFSR_DATA("0", 1),
LFSR_TAG_INLINED, 1, LFSR_DATA("1", 1)) => 0;
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == 3);
// try to find tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
lfsr_btree_namelookup(&lfs, &btree, 0*DID, "aaa", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 0);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "0", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 1*DID, "aab", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 1);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "1", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 2*DID, "aac", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 2);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "2", 1) == 0);
lfsr_btree_namelookup(&lfs, &btree, 3*DID, "aad", 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == 2);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, "2", 1) == 0);
'''
[cases.test_btree_find]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
# true or false for if we should use dids vs names
defines.DID = [false, true]
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *nums = "0123456789";
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA(&nums[0 % 10], 1)) => 0;
lfs_size_t n = 1;
for (lfs_size_t i = 1; i < N; i++) {
char name[3] = {
alphas[(i/26/26) % 26], alphas[(i/26) % 26], alphas[i % 26]
};
int err = lfsr_btree_split(&lfs, &btree, i-1,
LFSR_DATA_NAME(i*DID, name, 3),
LFSR_TAG_INLINED, 1, LFSR_DATA(&nums[(i-1) % 10], 1),
LFSR_TAG_INLINED, 1, LFSR_DATA(&nums[(i-0) % 10], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
n += 1;
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == n);
// try to find tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < n; i++) {
char name[3] = {
alphas[(i/26/26) % 26], alphas[(i/26) % 26], alphas[i % 26]
};
lfsr_btree_namelookup(&lfs, &btree, i*DID, name, 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == i);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &nums[i % 10], 1) == 0);
}
'''
[cases.test_btree_find_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *nums = "0123456789";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("_", 1)) => 0;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
char (*sim_names)[3] = malloc(N*3);
lfs_size_t sim_size = 1;
memset(sim, 0, N);
memset(sim_names, 0, N*3);
sim[0] = '_';
memcpy(&sim_names[0], "___", 3);
uint32_t prng = SEED;
for (lfs_size_t i = 1; i < N; i++) {
// choose a pseudo-random name
lfs_size_t x = TEST_PRNG(&prng) % (26*26*26);
char name[3] = {
alphas[(x/26/26) % 26], alphas[(x/26) % 26], alphas[x % 26]
};
// find where to split
lfs_size_t bid = 0;
while (bid+1 < sim_size && memcmp(sim_names[bid+1], name, 3) <= 0) {
bid += 1;
}
// just skip exact matches for now
if (memcmp(sim_names[bid], name, 3) == 0) {
continue;
}
// split btree
int err = lfsr_btree_split(&lfs, &btree, bid,
LFSR_DATA_NAME(0, name, 3),
LFSR_TAG_INLINED, 1, LFSR_DATA(&nums[i % 10], 1),
LFSR_TAG_INLINED, 1, LFSR_DATA(&nums[i % 10], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// split sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
memmove(&sim_names[bid+1], &sim_names[bid], (sim_size-bid)*3);
sim[bid+0] = nums[i % 10];
sim[bid+1] = nums[i % 10];
memcpy(&sim_names[bid+1], name, 3);
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%.3s=%c", sim_names[i], sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_namelookup(&lfs, &btree, 0, sim_names[i], 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == i);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// clean up sim
free(sim);
free(sim_names);
lfs_deinit(&lfs) => 0;
'''
[cases.test_btree_find_sparse]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.W = 5
# true or false for if we should use dids vs names
defines.DID = [false, true]
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *nums = "0123456789";
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, W,
LFSR_DATA(&nums[0 % 10], 1)) => 0;
lfs_size_t n = 1;
for (lfs_size_t i = 1; i < N; i++) {
char name[3] = {
alphas[(i/26/26) % 26], alphas[(i/26) % 26], alphas[i % 26]
};
int err = lfsr_btree_split(&lfs, &btree,
(i-1)*W+W-1, LFSR_DATA_NAME(i*DID, name, 3),
LFSR_TAG_INLINED, W, LFSR_DATA(&nums[(i-1) % 10], 1),
LFSR_TAG_INLINED, W, LFSR_DATA(&nums[(i-0) % 10], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
n += 1;
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == n*W);
// try to find tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < n; i++) {
char name[3] = {
alphas[(i/26/26) % 26], alphas[(i/26) % 26], alphas[i % 26]
};
lfsr_btree_namelookup(&lfs, &btree, i*DID, name, 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == i*W+W-1);
assert(weight_ == W);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &nums[i % 10], 1) == 0);
}
'''
[cases.test_btree_find_sparse_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.W = 5
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *nums = "0123456789";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, W,
LFSR_DATA("_", 1)) => 0;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
char (*sim_names)[3] = malloc(N*3);
lfs_size_t *sim_weights = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 1;
memset(sim, 0, N);
memset(sim_names, 0, N*3);
memset(sim_weights, 0, N*sizeof(lfs_size_t));
sim[0] = '_';
memcpy(&sim_names[0], "___", 3);
sim_weights[0] = W;
uint32_t prng = SEED;
for (lfs_size_t i = 1; i < N; i++) {
// choose a pseudo-random name
lfs_size_t x = TEST_PRNG(&prng) % (26*26*26);
char name[3] = {
alphas[(x/26/26) % 26], alphas[(x/26) % 26], alphas[x % 26]
};
// choose pseudo-random weights
lfs_size_t weight1 = 1 + (TEST_PRNG(&prng) % W);
lfs_size_t weight2 = 1 + (TEST_PRNG(&prng) % W);
// find where to split
lfs_size_t bid = 0;
while (bid+1 < sim_size && memcmp(sim_names[bid+1], name, 3) <= 0) {
bid += 1;
}
// just skip exact matches for now
if (memcmp(sim_names[bid], name, 3) == 0) {
continue;
}
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < bid; j++) {
weighted_bid += sim_weights[j];
}
// split btree
int err = lfsr_btree_split(&lfs, &btree,
weighted_bid+sim_weights[bid]-1,
LFSR_DATA_NAME(0, name, 3),
LFSR_TAG_INLINED, weight1, LFSR_DATA(&nums[i % 10], 1),
LFSR_TAG_INLINED, weight2, LFSR_DATA(&nums[i % 10], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// split sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
memmove(&sim_names[bid+1], &sim_names[bid], (sim_size-bid)*3);
memmove(&sim_weights[bid+1], &sim_weights[bid],
(sim_size-bid)*sizeof(lfs_size_t));
sim[bid+0] = nums[i % 10];
sim[bid+1] = nums[i % 10];
memcpy(&sim_names[bid+1], name, 3);
sim_weights[bid+0] = weight1;
sim_weights[bid+1] = weight2;
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%.3sid%dw%d=%c",
sim_names[i],
weighted_bid+sim_weights[i]-1,
sim_weights[i],
sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
total_weight += sim_weights[j];
}
assert(lfsr_btree_weight(&btree) == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_namelookup(&lfs, &btree, 0, sim_names[i], 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == weighted_bid+sim_weights[i]-1);
assert(weight_ == sim_weights[i]);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// clean up sim
free(sim);
free(sim_names);
free(sim_weights);
lfs_deinit(&lfs) => 0;
'''
# make sure we test finds with other operations
[cases.test_btree_find_general_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.SEED = 'range(100)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *nums = "0123456789";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
LFSR_DATA("_", 1)) => 0;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
char (*sim_names)[3] = malloc(N*3);
lfs_size_t sim_size = 1;
memset(sim, 0, N);
memset(sim_names, 0, N*3);
sim[0] = '_';
memcpy(&sim_names[0], "___", 3);
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random op
uint8_t op = TEST_PRNG(&prng) % 3;
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size == 0 ? 1 : sim_size);
// choose a pseudo-random name
lfs_size_t x = TEST_PRNG(&prng) % (26*26*26);
char name[3] = {
alphas[(x/26/26) % 26], alphas[(x/26) % 26], alphas[x % 26]
};
// don't let sim drop below one element
if (op == 0 || sim_size <= 1) {
// find where to split
lfs_size_t bid = 0;
while (bid+1 < sim_size
&& memcmp(sim_names[bid+1], name, 3) <= 0) {
bid += 1;
}
// just skip exact matches for now
if (memcmp(sim_names[bid], name, 3) == 0) {
continue;
}
// split btree
lfs_size_t split_bid;
lfsr_data_t split_data;
lfsr_btree_namelookup(&lfs, &btree, 0, name, 3,
&split_bid, NULL, NULL, &split_data) => 0;
uint8_t split_buf[4];
lfsr_data_read(&lfs, &split_data, split_buf, 4) => 1;
if (split_bid > bid) {
int err = lfsr_btree_split(&lfs, &btree,
split_bid,
LFSR_DATA_NAME(0, sim_names[bid+1], 3),
LFSR_TAG_INLINED, 1,
LFSR_DATA(&nums[i % 10], 1),
LFSR_TAG_INLINED, 1,
LFSR_DATA(split_buf, 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
} else {
int err = lfsr_btree_split(&lfs, &btree,
split_bid, LFSR_DATA_NAME(0, name, 3),
LFSR_TAG_INLINED, 1,
LFSR_DATA(split_buf, 1),
LFSR_TAG_INLINED, 1,
LFSR_DATA(&nums[i % 10], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
}
// split sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
memmove(&sim_names[bid+1], &sim_names[bid], (sim_size-bid)*3);
sim[bid+1] = nums[i % 10];
memcpy(&sim_names[bid+1], name, 3);
sim_size += 1;
} else if (op == 1) {
// update btree
int err = lfsr_btree_set(&lfs, &btree, bid,
LFSR_TAG_INLINED, 1,
LFSR_DATA(&nums[i % 10], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// update sim
sim[bid] = nums[i % 10];
} else {
// pop from btree
int err = lfsr_btree_pop(&lfs, &btree, bid);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// pop from sim
memmove(&sim[bid], &sim[bid+1], sim_size-(bid+1));
memmove(&sim_names[bid], &sim_names[bid+1], (sim_size-(bid+1))*3);
sim_size -= 1;
// our B-tree doesn't actually track the name of id0, so we need
// mirror this in our sim
if (bid == 0) {
memcpy(&sim_names[0], "___", 3);
}
}
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%.3s=%c", sim_names[i], sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_namelookup(&lfs, &btree, 0, sim_names[i], 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == i);
assert(weight_ == 1);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// clean up sim
free(sim);
free(sim_names);
'''
[cases.test_btree_find_general_sparse_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
defines.W = 5
defines.SEED = 'range(100)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *nums = "0123456789";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, W,
LFSR_DATA("_", 1)) => 0;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
char (*sim_names)[3] = malloc(N*3);
lfs_size_t *sim_weights = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 1;
memset(sim, 0, N);
memset(sim_names, 0, N*3);
memset(sim_weights, 0, N*sizeof(lfs_size_t));
sim[0] = '_';
memcpy(&sim_names[0], "___", 3);
sim_weights[0] = W;
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random op
uint8_t op = TEST_PRNG(&prng) % 3;
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size == 0 ? 1 : sim_size);
// choose a pseudo-random name
lfs_size_t x = TEST_PRNG(&prng) % (26*26*26);
char name[3] = {
alphas[(x/26/26) % 26], alphas[(x/26) % 26], alphas[x % 26]
};
// choose a pseudo-random weight
lfs_size_t weight = 1 + (TEST_PRNG(&prng) % W);
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < bid; j++) {
weighted_bid += sim_weights[j];
}
// don't let sim drop below one element
if (op == 0 || sim_size <= 1) {
// find where to split
lfs_size_t bid = 0;
while (bid+1 < sim_size
&& memcmp(sim_names[bid+1], name, 3) <= 0) {
bid += 1;
}
// just skip exact matches for now
if (memcmp(sim_names[bid], name, 3) == 0) {
continue;
}
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < bid; j++) {
weighted_bid += sim_weights[j];
}
// split btree
lfs_size_t split_bid;
lfs_size_t split_weight;
lfsr_data_t split_data;
lfsr_btree_namelookup(&lfs, &btree, 0, name, 3,
&split_bid, NULL, &split_weight,
&split_data) => 0;
uint8_t split_buf[4];
lfsr_data_read(&lfs, &split_data, split_buf, 4) => 1;
if (split_bid > weighted_bid+sim_weights[bid]-1) {
int err = lfsr_btree_split(&lfs, &btree,
split_bid, LFSR_DATA_NAME(0, sim_names[bid+1], 3),
LFSR_TAG_INLINED, weight,
LFSR_DATA(&nums[i % 10], 1),
LFSR_TAG_INLINED, split_weight,
LFSR_DATA(split_buf, 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
} else {
int err = lfsr_btree_split(&lfs, &btree,
split_bid, LFSR_DATA_NAME(0, name, 3),
LFSR_TAG_INLINED, split_weight,
LFSR_DATA(split_buf, 1),
LFSR_TAG_INLINED, weight,
LFSR_DATA(&nums[i % 10], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
}
// split sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
memmove(&sim_names[bid+1], &sim_names[bid], (sim_size-bid)*3);
memmove(&sim_weights[bid+1], &sim_weights[bid],
(sim_size-bid)*sizeof(lfs_size_t));
sim[bid+1] = nums[i % 10];
memcpy(&sim_names[bid+1], name, 3);
sim_weights[bid+1] = weight;
sim_size += 1;
} else if (op == 1) {
// update btree
int err = lfsr_btree_set(&lfs, &btree,
weighted_bid+sim_weights[bid]-1, LFSR_TAG_INLINED, weight,
LFSR_DATA(&nums[i % 10], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// update sim
sim[bid] = nums[i % 10];
sim_weights[bid] = weight;
} else {
// pop from btree
int err = lfsr_btree_pop(&lfs, &btree,
weighted_bid+sim_weights[bid]-1);
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// pop from sim
memmove(&sim[bid], &sim[bid+1], sim_size-(bid+1));
memmove(&sim_names[bid], &sim_names[bid+1], (sim_size-(bid+1))*3);
memmove(&sim_weights[bid], &sim_weights[bid+1],
(sim_size-(bid+1))*sizeof(lfs_size_t));
sim_size -= 1;
// our B-tree doesn't actually track the name of id0, so we need
// mirror this in our sim
if (bid == 0) {
memcpy(&sim_names[0], "___", 3);
}
}
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%.3sid%dw%d=%c",
sim_names[i],
weighted_bid+sim_weights[i]-1,
sim_weights[i],
sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
total_weight += sim_weights[j];
}
assert(lfsr_btree_weight(&btree) == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t bid_;
lfs_size_t weight_;
lfsr_data_t data_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual bid in btree space
lfs_size_t weighted_bid = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_bid += sim_weights[j];
}
lfsr_btree_namelookup(&lfs, &btree, 0, sim_names[i], 3,
&bid_, &tag_, &weight_, &data_) => 0;
assert(tag_ == LFSR_TAG_INLINED);
assert(bid_ == weighted_bid+sim_weights[i]-1);
assert(weight_ == sim_weights[i]);
lfsr_data_read(&lfs, &data_, buffer, 4) => 1;
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// clean up sim
free(sim);
free(sim_names);
free(sim_weights);
'''
## B-tree traversal tests ##
# some simple btree traversals
[cases.test_btree_traversal]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
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 a tree with N elements
lfsr_btree_t btree = LFSR_BTREE_NULL;
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
lfs_size_t n = 0;
for (lfs_size_t i = 0; i < N; i++) {
int err = lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
n += 1;
}
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == n);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < n; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, n,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// 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_btree_traversal_t traversal = LFSR_BTREE_TRAVERSAL;
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*N);
lfs_size_t bid_;
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_data_t data_;
int err = lfsr_btree_traversal_next(&lfs, &btree, &traversal,
&bid_, &tag_, &weight_, &data_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d 0x%x w%d btree 0x%x.%x\n",
bid_,
tag_,
weight_,
branch->block, branch->trunk);
// keep track of seen blocks
seen[branch->block / 8] |= 1 << (branch->block % 8);
} else {
printf("traversal: %d 0x%x w%d %d\n",
bid_,
tag_,
weight_,
lfsr_data_size(&data_));
}
}
// 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
// check that the elements are in the tree
for (lfs_size_t i = 0; i < n; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
// and check that we can't lookup elements that aren't in the tree
lfsr_btree_get(&lfs, &btree, n,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_traversal_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.SEED = 'range(10)'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
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 a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
// set up a simulation to compare against
//
// fun fact this is slower than our actual tree! unfun fact this is
// starting to be a problem...
char *sim = malloc(N);
lfs_size_t sim_size = 0;
memset(sim, 0, N);
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < N; i++) {
// choose a pseudo-random bid
lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1);
// add to btree
int err = lfsr_btree_push(&lfs, &btree, bid, LFSR_TAG_INLINED, 1,
LFSR_DATA(&alphas[i % 26], 1));
// ignore space issues
if (err == LFS_ERR_NOSPC) {
break;
}
assert(err == 0);
// add to sim
memmove(&sim[bid+1], &sim[bid], sim_size-bid);
sim[bid] = alphas[i % 26];
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, sim_size,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// 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_btree_traversal_t traversal = LFSR_BTREE_TRAVERSAL;
for (lfs_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*N);
lfs_size_t bid_;
lfsr_tag_t tag_;
lfs_size_t weight_;
lfsr_data_t data_;
int err = lfsr_btree_traversal_next(&lfs, &btree, &traversal,
&bid_, &tag_, &weight_, &data_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
if (tag_ == LFSR_TAG_BTREE) {
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
printf("traversal: %d 0x%x w%d btree 0x%x.%x\n",
bid_,
tag_,
weight_,
branch->block, branch->trunk);
// keep track of seen blocks
seen[branch->block / 8] |= 1 << (branch->block % 8);
} else {
printf("traversal: %d 0x%x w%d %d\n",
bid_,
tag_,
weight_,
lfsr_data_size(&data_));
}
}
// 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
// check that btree matches sim
printf("expd: [");
first = true;
for (lfs_size_t i = 0; i < sim_size; i++) {
if (!first) {
printf(", ");
}
first = false;
printf("%c", sim[i]);
}
printf("]\n");
printf("btree: w%d 0x%x.%x\n",
btree.u.r.rbyd.weight,
btree.u.r.rbyd.block,
btree.u.r.rbyd.trunk);
assert(lfsr_btree_weight(&btree) == sim_size);
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &weight_, buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, sim_size,
&tag_, &weight_, buffer, 4) => LFS_ERR_NOENT;
// clean up sim
free(sim);
lfs_deinit(&lfs) => 0;
'''