diff --git a/lfs.c b/lfs.c index bf434976..3d96de0a 100644 --- a/lfs.c +++ b/lfs.c @@ -2232,6 +2232,7 @@ static int lfsr_rbyd_append(lfs_t *lfs, lfsr_rbyd_t *rbyd, other_id_ = id_; } else if (tag == LFSR_TAG_SHRINK) { LFS_ASSERT(id < rbyd->weight); + LFS_ASSERT(lfsr_data_len(data) <= rbyd->weight); // noop? if (lfsr_data_len(data) == 0) { return 0; @@ -3320,8 +3321,6 @@ static lfs_ssize_t lfsr_btree_get(lfs_t *lfs, buffer, size); } -// TODO drop the root during merges -// TODO inline? static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree, lfs_size_t id, lfsr_rbyd_t *rbyd, const struct lfsr_attr *attrs) { @@ -3821,6 +3820,11 @@ static int lfsr_btree_commit(lfs_t *lfs, goto abort; } + // only child? can't merge + if (pweight == parent.weight) { + goto abort; + } + // last child? try the left sibling lfs_ssize_t sid; lfs_ssize_t sdelta; diff --git a/tests/test_btree.toml b/tests/test_btree.toml index b781d4e1..0424a453 100644 --- a/tests/test_btree.toml +++ b/tests/test_btree.toml @@ -452,7 +452,6 @@ code = ''' 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 @@ -2068,10 +2067,282 @@ code = ''' } ''' -# -## [cases.test_btree_split] -## [cases.test_btree_split_fuzz] -# -## [cases.test_btree_general_fuzz] -# -## TODO also test copy-on-write! + +# TODO [cases.test_btree_split] +# TODO [cases.test_btree_split_fuzz] + + + +# Some more general fuzz testing +[cases.test_btree_general_fuzz] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024] +defines.ITER = 100 +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-REMAINING); i++) { + // choose a pseudo-random op + uint8_t op = TEST_PRNG(&prng) % 3; + // choose a pseudo-random id + lfs_size_t id = TEST_PRNG(&prng) % (sim_size+1); + + if (op == 0 || id == sim_size) { + // push to btree + lfsr_btree_push(&lfs, &btree, id, + LFSR_TAG_INLINED, 1, + &alphas[i % 26], 1) => 0; + + // push to sim + memmove(&sim[id+1], &sim[id], sim_size-id); + sim[id] = alphas[i % 26]; + sim_size += 1; + + } else if (op == 1) { + // update btree + lfsr_btree_update(&lfs, &btree, id, + LFSR_TAG_INLINED, 1, + &alphas[i % 26], 1) => 0; + + // update sim + sim[id] = alphas[i % 26]; + + } else { + // pop from btree + lfsr_btree_pop(&lfs, &btree, id) => 0; + + // pop 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_general_sparse_fuzz] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024] +defines.W = 5 +defines.ITER = 100 +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-REMAINING); i++) { + // choose a pseudo-random op + uint8_t op = TEST_PRNG(&prng) % 3; + // 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]; + } + + if (op == 0 || id == sim_size) { + // push to btree + lfsr_btree_push(&lfs, &btree, weighted_id, + LFSR_TAG_INLINED, weight, + &alphas[i % 26], 1) => 0; + + // push 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; + + } else if (op == 1) { + // update btree + lfsr_btree_update(&lfs, &btree, + weighted_id+sim_weights[id]-1, LFSR_TAG_INLINED, weight, + &alphas[i % 26], 1) => 0; + + // update sim + sim[id] = alphas[i % 26]; + sim_weights[id] = weight; + + } else { + // 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++) { + 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); + + 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); + } +''' +