Files
littlefs/tests/test_btree.toml
T
Christopher Haster c55cc4d010 Fixed memcpy/memmove mistake in btree tests
A classic C footgun, overlapping mempcpys, that somtimes only fail when
optimizations are enabled. Adopting memmove as needed.
2023-03-17 14:54:48 -05:00

2078 lines
62 KiB
TOML

# maximize lookahead buffer, we don't actually gc so we only get one pass
# of the disk for these tests
defines.LOOKAHEAD_SIZE = 'BLOCK_COUNT / 8'
# test an empty tree
[cases.test_btree_zero]
in = 'lfs.c'
code = '''
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
// create free lookahead
memset(lfs.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 0;
lfs_alloc_ack(&lfs);
// create an empty tree
lfsr_btree_t btree = LFSR_BTREE_NULL;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 0);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 1);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "b", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 2);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "b", 1) => 0;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1, "a", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 2);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "b", 1) => 0;
lfsr_btree_push(&lfs, &btree, 2, LFSR_TAG_INLINED, 1, "c", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 3);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 2);
assert(weight_ == 1);
assert(memcmp(buffer, "c", 1) == 0);
lfsr_btree_get(&lfs, &btree, 3,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "c", 1) => 0;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1, "b", 1) => 0;
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1, "a", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 3);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 2);
assert(weight_ == 1);
assert(memcmp(buffer, "c", 1) == 0);
lfsr_btree_get(&lfs, &btree, 3,
&tag_, &id_, &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 = [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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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++) {
lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
&alphas[i % 26], 1) => 0;
}
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == N);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
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_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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++) {
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
&alphas[(N-1-i) % 26], 1) => 0;
}
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == N);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
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_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_push_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.ITER = 10
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = 1; seed < ITER+1; seed++) {
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
// create free lookahead
memset(lfs.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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 id
lfs_size_t id = TEST_PRNG(&prng) % (sim_size+1);
// add to btree
lfsr_btree_push(&lfs, &btree, id, LFSR_TAG_INLINED, 1,
&alphas[i % 26], 1) => 0;
// add to sim
memmove(&sim[id+1], &sim[id], sim_size-id);
sim[id] = alphas[i % 26];
sim_size += 1;
printf("%c\n", alphas[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: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == N);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, N,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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++) {
lfsr_btree_push(&lfs, &btree, i*W, LFSR_TAG_INLINED, W,
&alphas[i % 26], 1) => 0;
}
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == N*W);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i*W+W-1);
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_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
id_ = -1;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i*W+W-1);
assert(weight_ == W);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 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.ITER = 10
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = 1; seed < ITER+1; seed++) {
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
// create free lookahead
memset(lfs.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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 id
lfs_size_t id = TEST_PRNG(&prng) % (sim_size+1);
// choose a pseudo-random weight
lfs_size_t weight = 1 + (TEST_PRNG(&prng) % W);
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < id; j++) {
weighted_id += sim_weights[j];
}
// add to btree
lfsr_btree_push(&lfs, &btree, weighted_id, LFSR_TAG_INLINED, weight,
&alphas[i % 26], 1) => 0;
// add to sim
memmove(&sim[id+1], &sim[id], sim_size-id);
memmove(&sim_weights[id+1], &sim_weights[id],
(sim_size-id)*sizeof(lfs_size_t));
sim[id] = alphas[i % 26];
sim_weights[id] = weight;
sim_size += 1;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%dw%d=%c", weighted_id+sim_weights[i]-1,
sim_weights[i], sim[i]);
}
printf("]\n");
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < N; j++) {
total_weight += sim_weights[j];
}
assert(btree.weight == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_id+sim_weights[i]-1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == weighted_id+sim_weights[i]-1);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, total_weight,
&tag_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
id_ = -1;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == weighted_id+sim_weights[i]-1);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
// update the tree
lfsr_btree_update(&lfs, &btree, 0, LFSR_TAG_INLINED, 1, "A", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 1);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "b", 1) => 0;
// update the tree
lfsr_btree_update(&lfs, &btree, 0, LFSR_TAG_INLINED, 1, "A", 1) => 0;
lfsr_btree_update(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "B", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 2);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "B", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "b", 1) => 0;
lfsr_btree_push(&lfs, &btree, 2, LFSR_TAG_INLINED, 1, "c", 1) => 0;
// update the tree
lfsr_btree_update(&lfs, &btree, 0, LFSR_TAG_INLINED, 1, "A", 1) => 0;
lfsr_btree_update(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "B", 1) => 0;
lfsr_btree_update(&lfs, &btree, 2, LFSR_TAG_INLINED, 1, "C", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 3);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "B", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 2);
assert(weight_ == 1);
assert(memcmp(buffer, "C", 1) == 0);
lfsr_btree_get(&lfs, &btree, 3,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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++) {
lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
&alphas[i % 26], 1) => 0;
}
// update the tree
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_update(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
&uppers[i % 26], 1) => 0;
}
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == N);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
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_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_update_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.ITER = 10
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = 1; seed < ITER+1; seed++) {
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
// create free lookahead
memset(lfs.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 0;
lfs_alloc_ack(&lfs);
// create a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
&alphas[i % 26], 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);
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 id
lfs_size_t id = TEST_PRNG(&prng) % N;
// update btree
lfsr_btree_update(&lfs, &btree, id, LFSR_TAG_INLINED, 1,
&uppers[i % 26], 1) => 0;
// update sim
sim[id] = 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: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == N);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, N,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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++) {
lfsr_btree_push(&lfs, &btree, i*W, LFSR_TAG_INLINED, W,
&alphas[i % 26], 1) => 0;
}
// update the tree
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_update(&lfs, &btree, i*W+W-1, LFSR_TAG_INLINED, W,
&uppers[i % 26], 1) => 0;
}
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == N*W);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i*W+W-1);
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_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
id_ = -1;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i*W+W-1);
assert(weight_ == W);
assert(memcmp(buffer, &uppers[i % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 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.ITER = 10
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
const char *uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = 1; seed < ITER+1; seed++) {
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
// create free lookahead
memset(lfs.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 0;
lfs_alloc_ack(&lfs);
// create a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_push(&lfs, &btree, i*W, LFSR_TAG_INLINED, W,
&alphas[i % 26], 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));
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 id
lfs_size_t id = TEST_PRNG(&prng) % N;
// choose a pseudo-random weight
lfs_size_t weight = 1 + (TEST_PRNG(&prng) % W);
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < id; j++) {
weighted_id += sim_weights[j];
}
// update btree
lfsr_btree_update(&lfs, &btree,
weighted_id+sim_weights[id]-1, LFSR_TAG_INLINED, weight,
&uppers[i % 26], 1) => 0;
// update sim
sim[id] = uppers[i % 26];
sim_weights[id] = weight;
}
// check that btree matches sim
printf("expd: [");
bool first = true;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%dw%d=%c", weighted_id+sim_weights[i]-1,
sim_weights[i], sim[i]);
}
printf("]\n");
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < N; j++) {
total_weight += sim_weights[j];
}
assert(btree.weight == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_id+sim_weights[i]-1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == weighted_id+sim_weights[i]-1);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, total_weight,
&tag_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
id_ = -1;
for (lfs_size_t i = 0; i < N; i++) {
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == weighted_id+sim_weights[i]-1);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
// pop!
lfsr_btree_pop(&lfs, &btree, 0) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 0);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &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, "A", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 1);
// try looking up tags
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "b", 1) => 0;
// pop!
lfsr_btree_pop(&lfs, &btree, 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 1);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &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, "B", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 2);
// try looking up tags
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "B", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "b", 1) => 0;
// pop!
lfsr_btree_pop(&lfs, &btree, 0) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 1);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &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, "A", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 2);
// try looking up tags
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "A", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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, "a", 1) => 0;
lfsr_btree_push(&lfs, &btree, 1, LFSR_TAG_INLINED, 1, "b", 1) => 0;
lfsr_btree_push(&lfs, &btree, 2, LFSR_TAG_INLINED, 1, "c", 1) => 0;
// pop!
lfsr_btree_pop(&lfs, &btree, 2) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 2);
// try looking up tags
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &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, "C", 1) => 0;
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == 3);
// try looking up tags
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
assert(weight_ == 1);
assert(memcmp(buffer, "a", 1) == 0);
lfsr_btree_get(&lfs, &btree, 1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 1);
assert(weight_ == 1);
assert(memcmp(buffer, "b", 1) == 0);
lfsr_btree_get(&lfs, &btree, 2,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 2);
assert(weight_ == 1);
assert(memcmp(buffer, "C", 1) == 0);
lfsr_btree_get(&lfs, &btree, 3,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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++) {
lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
&alphas[i % 26], 1) => 0;
}
// drain the tree
for (lfs_size_t i = 0; i < N-REMAINING; i++) {
lfsr_btree_pop(&lfs, &btree, N-1-i) => 0;
}
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == REMAINING);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
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_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// try recovering
lfsr_btree_push(&lfs, &btree, REMAINING, LFSR_TAG_INLINED, 1,
"R", 1) => 0;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, REMAINING,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == REMAINING);
assert(weight_ == 1);
assert(memcmp(buffer, "R", 1) == 0);
lfsr_btree_get(&lfs, &btree, REMAINING+1,
&tag_, &id_, &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.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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++) {
lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
&alphas[i % 26], 1) => 0;
}
// drain the tree
for (lfs_size_t i = 0; i < N-REMAINING; i++) {
lfsr_btree_pop(&lfs, &btree, 0) => 0;
}
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == REMAINING);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
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_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// try recovering
lfsr_btree_push(&lfs, &btree, 0, LFSR_TAG_INLINED, 1,
"R", 1) => 0;
lfsr_btree_get(&lfs, &btree, 0,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == 0);
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_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i+1);
assert(weight_ == 1);
assert(memcmp(buffer, &alphas[(i+(N-REMAINING)) % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, REMAINING+1,
&tag_, &id_, &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.ITER = 10
if = 'N > REMAINING'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = 1; seed < ITER+1; seed++) {
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
// create free lookahead
memset(lfs.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 0;
lfs_alloc_ack(&lfs);
// create a btree
lfsr_btree_t btree = LFSR_BTREE_NULL;
for (lfs_size_t i = 0; i < N; i++) {
lfsr_btree_push(&lfs, &btree, i, LFSR_TAG_INLINED, 1,
&alphas[i % 26], 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 = 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 id
lfs_size_t id = TEST_PRNG(&prng) % sim_size;
// remove from btree
lfsr_btree_pop(&lfs, &btree, id) => 0;
// remove from sim
memmove(&sim[id], &sim[id+1], sim_size-(id+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: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == sim_size);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
lfsr_btree_get(&lfs, &btree, i,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i);
assert(weight_ == 1);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, sim_size,
&tag_, &id_, &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.REMAINING = [64, 2, 1, 0]
defines.W = 5
if = 'N > REMAINING'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
// create free lookahead
memset(lfs.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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++) {
lfsr_btree_push(&lfs, &btree, i*W, LFSR_TAG_INLINED, W,
&alphas[i % 26], 1) => 0;
}
// drain the tree
for (lfs_size_t i = 0; i < N-REMAINING; i++) {
lfsr_btree_pop(&lfs, &btree, (N-1-i)*W+W-1) => 0;
}
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
assert(btree.weight == REMAINING*W);
// check that the elements are in the tree
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i*W+W-1);
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_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// try recovering
lfsr_btree_push(&lfs, &btree, REMAINING*W, LFSR_TAG_INLINED, W,
"R", 1) => 0;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, i*W+W-1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i*W+W-1);
assert(weight_ == W);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, REMAINING*W+W-1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == REMAINING*W+W-1);
assert(weight_ == W);
assert(memcmp(buffer, "R", 1) == 0);
lfsr_btree_get(&lfs, &btree, (REMAINING+1)*W+W-1,
&tag_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
id_ = -1;
for (lfs_size_t i = 0; i < REMAINING; i++) {
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == i*W+W-1);
assert(weight_ == W);
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == REMAINING*W+W-1);
assert(weight_ == W);
assert(memcmp(buffer, "R", 1) == 0);
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
'''
[cases.test_btree_pop_sparse_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.REMAINING = [64, 2, 1, 0]
defines.W = 5
defines.ITER = 10
if = 'N > REMAINING'
in = 'lfs.c'
code = '''
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
// iterate through severals seeds that we can reproduce easily
for (uint32_t seed = 1; seed < ITER+1; seed++) {
// create lfs here since we need to reset each iteration, we're
// space constrained and we can't expect gc to work at this point
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
// create free lookahead
memset(lfs.free.buffer, 0, lfs.cfg->lookahead_size);
lfs.free.off = 0;
lfs.free.size = lfs_min(8*lfs.cfg->lookahead_size,
lfs.cfg->block_count);
lfs.free.i = 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 id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
lfsr_btree_push(&lfs, &btree, weighted_id, LFSR_TAG_INLINED, weight,
&alphas[i % 26], 1) => 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 id
lfs_size_t id = TEST_PRNG(&prng) % sim_size;
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < id; j++) {
weighted_id += sim_weights[j];
}
// remove from btree
lfsr_btree_pop(&lfs, &btree, weighted_id+sim_weights[id]-1) => 0;
// remove from sim
memmove(&sim[id], &sim[id+1], sim_size-(id+1));
memmove(&sim_weights[id], &sim_weights[id+1],
(sim_size-(id+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 id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
if (!first) {
printf(", ");
}
first = false;
printf("%dw%d=%c", weighted_id+sim_weights[i]-1,
sim_weights[i], sim[i]);
}
printf("]\n");
printf("btree: 0x%x.%x 0x%x w%d\n",
btree.u.trunk.block,
btree.u.trunk.limit,
btree.tag,
btree.weight);
lfs_size_t total_weight = 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
total_weight += sim_weights[j];
}
assert(btree.weight == total_weight);
uint8_t buffer[4];
lfsr_tag_t tag_;
lfs_size_t id_;
lfs_size_t weight_;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, weighted_id+sim_weights[i]-1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == weighted_id+sim_weights[i]-1);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
// and no extra elements
lfsr_btree_get(&lfs, &btree, total_weight,
&tag_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// also test that we can traverse the tree without prior knowledge
id_ = -1;
for (lfs_size_t i = 0; i < sim_size; i++) {
// calculate actual id in btree space
lfs_size_t weighted_id = 0;
for (lfs_size_t j = 0; j < i; j++) {
weighted_id += sim_weights[j];
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => 1;
assert(tag_ == LFSR_TAG_INLINED);
assert(id_ == weighted_id+sim_weights[i]-1);
assert(weight_ == sim_weights[i]);
assert(memcmp(buffer, &sim[i], 1) == 0);
}
lfsr_btree_get(&lfs, &btree, id_+1,
&tag_, &id_, &weight_,
buffer, 4) => LFS_ERR_NOENT;
// clean up sim
free(sim);
}
'''
#
## [cases.test_btree_split]
## [cases.test_btree_split_fuzz]
#
## [cases.test_btree_general_fuzz]
#
## TODO also test copy-on-write!