summaryrefslogtreecommitdiffstats
path: root/deps/jemalloc/test/unit/rb.c
diff options
context:
space:
mode:
Diffstat (limited to 'deps/jemalloc/test/unit/rb.c')
-rw-r--r--deps/jemalloc/test/unit/rb.c1019
1 files changed, 1019 insertions, 0 deletions
diff --git a/deps/jemalloc/test/unit/rb.c b/deps/jemalloc/test/unit/rb.c
new file mode 100644
index 0000000..827ec51
--- /dev/null
+++ b/deps/jemalloc/test/unit/rb.c
@@ -0,0 +1,1019 @@
+#include "test/jemalloc_test.h"
+
+#include <stdlib.h>
+
+#include "jemalloc/internal/rb.h"
+
+#define rbtn_black_height(a_type, a_field, a_rbt, r_height) do { \
+ a_type *rbp_bh_t; \
+ for (rbp_bh_t = (a_rbt)->rbt_root, (r_height) = 0; rbp_bh_t != \
+ NULL; rbp_bh_t = rbtn_left_get(a_type, a_field, \
+ rbp_bh_t)) { \
+ if (!rbtn_red_get(a_type, a_field, rbp_bh_t)) { \
+ (r_height)++; \
+ } \
+ } \
+} while (0)
+
+static bool summarize_always_returns_true = false;
+
+typedef struct node_s node_t;
+struct node_s {
+#define NODE_MAGIC 0x9823af7e
+ uint32_t magic;
+ rb_node(node_t) link;
+ /* Order used by nodes. */
+ uint64_t key;
+ /*
+ * Our made-up summary property is "specialness", with summarization
+ * taking the max.
+ */
+ uint64_t specialness;
+
+ /*
+ * Used by some of the test randomization to avoid double-removing
+ * nodes.
+ */
+ bool mid_remove;
+
+ /*
+ * To test searching functionality, we want to temporarily weaken the
+ * ordering to allow non-equal nodes that nevertheless compare equal.
+ */
+ bool allow_duplicates;
+
+ /*
+ * In check_consistency, it's handy to know a node's rank in the tree;
+ * this tracks it (but only there; not all tests use this).
+ */
+ int rank;
+ int filtered_rank;
+
+ /*
+ * Replicate the internal structure of the tree, to make sure the
+ * implementation doesn't miss any updates.
+ */
+ const node_t *summary_lchild;
+ const node_t *summary_rchild;
+ uint64_t summary_max_specialness;
+};
+
+static int
+node_cmp(const node_t *a, const node_t *b) {
+ int ret;
+
+ expect_u32_eq(a->magic, NODE_MAGIC, "Bad magic");
+ expect_u32_eq(b->magic, NODE_MAGIC, "Bad magic");
+
+ ret = (a->key > b->key) - (a->key < b->key);
+ if (ret == 0 && !a->allow_duplicates) {
+ /*
+ * Duplicates are not allowed in the tree, so force an
+ * arbitrary ordering for non-identical items with equal keys,
+ * unless the user is searching and wants to allow the
+ * duplicate.
+ */
+ ret = (((uintptr_t)a) > ((uintptr_t)b))
+ - (((uintptr_t)a) < ((uintptr_t)b));
+ }
+ return ret;
+}
+
+static uint64_t
+node_subtree_specialness(node_t *n, const node_t *lchild,
+ const node_t *rchild) {
+ uint64_t subtree_specialness = n->specialness;
+ if (lchild != NULL
+ && lchild->summary_max_specialness > subtree_specialness) {
+ subtree_specialness = lchild->summary_max_specialness;
+ }
+ if (rchild != NULL
+ && rchild->summary_max_specialness > subtree_specialness) {
+ subtree_specialness = rchild->summary_max_specialness;
+ }
+ return subtree_specialness;
+}
+
+static bool
+node_summarize(node_t *a, const node_t *lchild, const node_t *rchild) {
+ uint64_t new_summary_max_specialness = node_subtree_specialness(
+ a, lchild, rchild);
+ bool changed = (a->summary_lchild != lchild)
+ || (a->summary_rchild != rchild)
+ || (new_summary_max_specialness != a->summary_max_specialness);
+ a->summary_max_specialness = new_summary_max_specialness;
+ a->summary_lchild = lchild;
+ a->summary_rchild = rchild;
+ return changed || summarize_always_returns_true;
+}
+
+typedef rb_tree(node_t) tree_t;
+rb_summarized_proto(static, tree_, tree_t, node_t);
+rb_summarized_gen(static, tree_, tree_t, node_t, link, node_cmp,
+ node_summarize);
+
+static bool
+specialness_filter_node(void *ctx, node_t *node) {
+ uint64_t specialness = *(uint64_t *)ctx;
+ return node->specialness >= specialness;
+}
+
+static bool
+specialness_filter_subtree(void *ctx, node_t *node) {
+ uint64_t specialness = *(uint64_t *)ctx;
+ return node->summary_max_specialness >= specialness;
+}
+
+static node_t *
+tree_iterate_cb(tree_t *tree, node_t *node, void *data) {
+ unsigned *i = (unsigned *)data;
+ node_t *search_node;
+
+ expect_u32_eq(node->magic, NODE_MAGIC, "Bad magic");
+
+ /* Test rb_search(). */
+ search_node = tree_search(tree, node);
+ expect_ptr_eq(search_node, node,
+ "tree_search() returned unexpected node");
+
+ /* Test rb_nsearch(). */
+ search_node = tree_nsearch(tree, node);
+ expect_ptr_eq(search_node, node,
+ "tree_nsearch() returned unexpected node");
+
+ /* Test rb_psearch(). */
+ search_node = tree_psearch(tree, node);
+ expect_ptr_eq(search_node, node,
+ "tree_psearch() returned unexpected node");
+
+ (*i)++;
+
+ return NULL;
+}
+
+TEST_BEGIN(test_rb_empty) {
+ tree_t tree;
+ node_t key;
+
+ tree_new(&tree);
+
+ expect_true(tree_empty(&tree), "Tree should be empty");
+ expect_ptr_null(tree_first(&tree), "Unexpected node");
+ expect_ptr_null(tree_last(&tree), "Unexpected node");
+
+ key.key = 0;
+ key.magic = NODE_MAGIC;
+ expect_ptr_null(tree_search(&tree, &key), "Unexpected node");
+
+ key.key = 0;
+ key.magic = NODE_MAGIC;
+ expect_ptr_null(tree_nsearch(&tree, &key), "Unexpected node");
+
+ key.key = 0;
+ key.magic = NODE_MAGIC;
+ expect_ptr_null(tree_psearch(&tree, &key), "Unexpected node");
+
+ unsigned nodes = 0;
+ tree_iter_filtered(&tree, NULL, &tree_iterate_cb,
+ &nodes, &specialness_filter_node, &specialness_filter_subtree,
+ NULL);
+ expect_u_eq(0, nodes, "");
+
+ nodes = 0;
+ tree_reverse_iter_filtered(&tree, NULL, &tree_iterate_cb,
+ &nodes, &specialness_filter_node, &specialness_filter_subtree,
+ NULL);
+ expect_u_eq(0, nodes, "");
+
+ expect_ptr_null(tree_first_filtered(&tree, &specialness_filter_node,
+ &specialness_filter_subtree, NULL), "");
+ expect_ptr_null(tree_last_filtered(&tree, &specialness_filter_node,
+ &specialness_filter_subtree, NULL), "");
+
+ key.key = 0;
+ key.magic = NODE_MAGIC;
+ expect_ptr_null(tree_search_filtered(&tree, &key,
+ &specialness_filter_node, &specialness_filter_subtree, NULL), "");
+ expect_ptr_null(tree_nsearch_filtered(&tree, &key,
+ &specialness_filter_node, &specialness_filter_subtree, NULL), "");
+ expect_ptr_null(tree_psearch_filtered(&tree, &key,
+ &specialness_filter_node, &specialness_filter_subtree, NULL), "");
+}
+TEST_END
+
+static unsigned
+tree_recurse(node_t *node, unsigned black_height, unsigned black_depth) {
+ unsigned ret = 0;
+ node_t *left_node;
+ node_t *right_node;
+
+ if (node == NULL) {
+ return ret;
+ }
+
+ left_node = rbtn_left_get(node_t, link, node);
+ right_node = rbtn_right_get(node_t, link, node);
+
+ expect_ptr_eq(left_node, node->summary_lchild,
+ "summary missed a tree update");
+ expect_ptr_eq(right_node, node->summary_rchild,
+ "summary missed a tree update");
+
+ uint64_t expected_subtree_specialness = node_subtree_specialness(node,
+ left_node, right_node);
+ expect_u64_eq(expected_subtree_specialness,
+ node->summary_max_specialness, "Incorrect summary");
+
+ if (!rbtn_red_get(node_t, link, node)) {
+ black_depth++;
+ }
+
+ /* Red nodes must be interleaved with black nodes. */
+ if (rbtn_red_get(node_t, link, node)) {
+ if (left_node != NULL) {
+ expect_false(rbtn_red_get(node_t, link, left_node),
+ "Node should be black");
+ }
+ if (right_node != NULL) {
+ expect_false(rbtn_red_get(node_t, link, right_node),
+ "Node should be black");
+ }
+ }
+
+ /* Self. */
+ expect_u32_eq(node->magic, NODE_MAGIC, "Bad magic");
+
+ /* Left subtree. */
+ if (left_node != NULL) {
+ ret += tree_recurse(left_node, black_height, black_depth);
+ } else {
+ ret += (black_depth != black_height);
+ }
+
+ /* Right subtree. */
+ if (right_node != NULL) {
+ ret += tree_recurse(right_node, black_height, black_depth);
+ } else {
+ ret += (black_depth != black_height);
+ }
+
+ return ret;
+}
+
+static unsigned
+tree_iterate(tree_t *tree) {
+ unsigned i;
+
+ i = 0;
+ tree_iter(tree, NULL, tree_iterate_cb, (void *)&i);
+
+ return i;
+}
+
+static unsigned
+tree_iterate_reverse(tree_t *tree) {
+ unsigned i;
+
+ i = 0;
+ tree_reverse_iter(tree, NULL, tree_iterate_cb, (void *)&i);
+
+ return i;
+}
+
+static void
+node_remove(tree_t *tree, node_t *node, unsigned nnodes) {
+ node_t *search_node;
+ unsigned black_height, imbalances;
+
+ tree_remove(tree, node);
+
+ /* Test rb_nsearch(). */
+ search_node = tree_nsearch(tree, node);
+ if (search_node != NULL) {
+ expect_u64_ge(search_node->key, node->key,
+ "Key ordering error");
+ }
+
+ /* Test rb_psearch(). */
+ search_node = tree_psearch(tree, node);
+ if (search_node != NULL) {
+ expect_u64_le(search_node->key, node->key,
+ "Key ordering error");
+ }
+
+ node->magic = 0;
+
+ rbtn_black_height(node_t, link, tree, black_height);
+ imbalances = tree_recurse(tree->rbt_root, black_height, 0);
+ expect_u_eq(imbalances, 0, "Tree is unbalanced");
+ expect_u_eq(tree_iterate(tree), nnodes-1,
+ "Unexpected node iteration count");
+ expect_u_eq(tree_iterate_reverse(tree), nnodes-1,
+ "Unexpected node iteration count");
+}
+
+static node_t *
+remove_iterate_cb(tree_t *tree, node_t *node, void *data) {
+ unsigned *nnodes = (unsigned *)data;
+ node_t *ret = tree_next(tree, node);
+
+ node_remove(tree, node, *nnodes);
+
+ return ret;
+}
+
+static node_t *
+remove_reverse_iterate_cb(tree_t *tree, node_t *node, void *data) {
+ unsigned *nnodes = (unsigned *)data;
+ node_t *ret = tree_prev(tree, node);
+
+ node_remove(tree, node, *nnodes);
+
+ return ret;
+}
+
+static void
+destroy_cb(node_t *node, void *data) {
+ unsigned *nnodes = (unsigned *)data;
+
+ expect_u_gt(*nnodes, 0, "Destruction removed too many nodes");
+ (*nnodes)--;
+}
+
+TEST_BEGIN(test_rb_random) {
+ enum {
+ NNODES = 25,
+ NBAGS = 500,
+ SEED = 42
+ };
+ sfmt_t *sfmt;
+ uint64_t bag[NNODES];
+ tree_t tree;
+ node_t nodes[NNODES];
+ unsigned i, j, k, black_height, imbalances;
+
+ sfmt = init_gen_rand(SEED);
+ for (i = 0; i < NBAGS; i++) {
+ switch (i) {
+ case 0:
+ /* Insert in order. */
+ for (j = 0; j < NNODES; j++) {
+ bag[j] = j;
+ }
+ break;
+ case 1:
+ /* Insert in reverse order. */
+ for (j = 0; j < NNODES; j++) {
+ bag[j] = NNODES - j - 1;
+ }
+ break;
+ default:
+ for (j = 0; j < NNODES; j++) {
+ bag[j] = gen_rand64_range(sfmt, NNODES);
+ }
+ }
+
+ /*
+ * We alternate test behavior with a period of 2 here, and a
+ * period of 5 down below, so there's no cycle in which certain
+ * combinations get omitted.
+ */
+ summarize_always_returns_true = (i % 2 == 0);
+
+ for (j = 1; j <= NNODES; j++) {
+ /* Initialize tree and nodes. */
+ tree_new(&tree);
+ for (k = 0; k < j; k++) {
+ nodes[k].magic = NODE_MAGIC;
+ nodes[k].key = bag[k];
+ nodes[k].specialness = gen_rand64_range(sfmt,
+ NNODES);
+ nodes[k].mid_remove = false;
+ nodes[k].allow_duplicates = false;
+ nodes[k].summary_lchild = NULL;
+ nodes[k].summary_rchild = NULL;
+ nodes[k].summary_max_specialness = 0;
+ }
+
+ /* Insert nodes. */
+ for (k = 0; k < j; k++) {
+ tree_insert(&tree, &nodes[k]);
+
+ rbtn_black_height(node_t, link, &tree,
+ black_height);
+ imbalances = tree_recurse(tree.rbt_root,
+ black_height, 0);
+ expect_u_eq(imbalances, 0,
+ "Tree is unbalanced");
+
+ expect_u_eq(tree_iterate(&tree), k+1,
+ "Unexpected node iteration count");
+ expect_u_eq(tree_iterate_reverse(&tree), k+1,
+ "Unexpected node iteration count");
+
+ expect_false(tree_empty(&tree),
+ "Tree should not be empty");
+ expect_ptr_not_null(tree_first(&tree),
+ "Tree should not be empty");
+ expect_ptr_not_null(tree_last(&tree),
+ "Tree should not be empty");
+
+ tree_next(&tree, &nodes[k]);
+ tree_prev(&tree, &nodes[k]);
+ }
+
+ /* Remove nodes. */
+ switch (i % 5) {
+ case 0:
+ for (k = 0; k < j; k++) {
+ node_remove(&tree, &nodes[k], j - k);
+ }
+ break;
+ case 1:
+ for (k = j; k > 0; k--) {
+ node_remove(&tree, &nodes[k-1], k);
+ }
+ break;
+ case 2: {
+ node_t *start;
+ unsigned nnodes = j;
+
+ start = NULL;
+ do {
+ start = tree_iter(&tree, start,
+ remove_iterate_cb, (void *)&nnodes);
+ nnodes--;
+ } while (start != NULL);
+ expect_u_eq(nnodes, 0,
+ "Removal terminated early");
+ break;
+ } case 3: {
+ node_t *start;
+ unsigned nnodes = j;
+
+ start = NULL;
+ do {
+ start = tree_reverse_iter(&tree, start,
+ remove_reverse_iterate_cb,
+ (void *)&nnodes);
+ nnodes--;
+ } while (start != NULL);
+ expect_u_eq(nnodes, 0,
+ "Removal terminated early");
+ break;
+ } case 4: {
+ unsigned nnodes = j;
+ tree_destroy(&tree, destroy_cb, &nnodes);
+ expect_u_eq(nnodes, 0,
+ "Destruction terminated early");
+ break;
+ } default:
+ not_reached();
+ }
+ }
+ }
+ fini_gen_rand(sfmt);
+}
+TEST_END
+
+static void
+expect_simple_consistency(tree_t *tree, uint64_t specialness,
+ bool expected_empty, node_t *expected_first, node_t *expected_last) {
+ bool empty;
+ node_t *first;
+ node_t *last;
+
+ empty = tree_empty_filtered(tree, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_b_eq(expected_empty, empty, "");
+
+ first = tree_first_filtered(tree,
+ &specialness_filter_node, &specialness_filter_subtree,
+ (void *)&specialness);
+ expect_ptr_eq(expected_first, first, "");
+
+ last = tree_last_filtered(tree,
+ &specialness_filter_node, &specialness_filter_subtree,
+ (void *)&specialness);
+ expect_ptr_eq(expected_last, last, "");
+}
+
+TEST_BEGIN(test_rb_filter_simple) {
+ enum {FILTER_NODES = 10};
+ node_t nodes[FILTER_NODES];
+ for (unsigned i = 0; i < FILTER_NODES; i++) {
+ nodes[i].magic = NODE_MAGIC;
+ nodes[i].key = i;
+ if (i == 0) {
+ nodes[i].specialness = 0;
+ } else {
+ nodes[i].specialness = ffs_u(i);
+ }
+ nodes[i].mid_remove = false;
+ nodes[i].allow_duplicates = false;
+ nodes[i].summary_lchild = NULL;
+ nodes[i].summary_rchild = NULL;
+ nodes[i].summary_max_specialness = 0;
+ }
+
+ summarize_always_returns_true = false;
+
+ tree_t tree;
+ tree_new(&tree);
+
+ /* Should be empty */
+ expect_simple_consistency(&tree, /* specialness */ 0, /* empty */ true,
+ /* first */ NULL, /* last */ NULL);
+
+ /* Fill in just the odd nodes. */
+ for (int i = 1; i < FILTER_NODES; i += 2) {
+ tree_insert(&tree, &nodes[i]);
+ }
+
+ /* A search for an odd node should succeed. */
+ expect_simple_consistency(&tree, /* specialness */ 0, /* empty */ false,
+ /* first */ &nodes[1], /* last */ &nodes[9]);
+
+ /* But a search for an even one should fail. */
+ expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ true,
+ /* first */ NULL, /* last */ NULL);
+
+ /* Now we add an even. */
+ tree_insert(&tree, &nodes[4]);
+ expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ false,
+ /* first */ &nodes[4], /* last */ &nodes[4]);
+
+ /* A smaller even, and a larger even. */
+ tree_insert(&tree, &nodes[2]);
+ tree_insert(&tree, &nodes[8]);
+
+ /*
+ * A first-search (resp. last-search) for an even should switch to the
+ * lower (higher) one, now that it's been added.
+ */
+ expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ false,
+ /* first */ &nodes[2], /* last */ &nodes[8]);
+
+ /*
+ * If we remove 2, a first-search we should go back to 4, while a
+ * last-search should remain unchanged.
+ */
+ tree_remove(&tree, &nodes[2]);
+ expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ false,
+ /* first */ &nodes[4], /* last */ &nodes[8]);
+
+ /* Reinsert 2, then find it again. */
+ tree_insert(&tree, &nodes[2]);
+ expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ false,
+ /* first */ &nodes[2], /* last */ &nodes[8]);
+
+ /* Searching for a multiple of 4 should not have changed. */
+ expect_simple_consistency(&tree, /* specialness */ 2, /* empty */ false,
+ /* first */ &nodes[4], /* last */ &nodes[8]);
+
+ /* And a multiple of 8 */
+ expect_simple_consistency(&tree, /* specialness */ 3, /* empty */ false,
+ /* first */ &nodes[8], /* last */ &nodes[8]);
+
+ /* But not a multiple of 16 */
+ expect_simple_consistency(&tree, /* specialness */ 4, /* empty */ true,
+ /* first */ NULL, /* last */ NULL);
+}
+TEST_END
+
+typedef struct iter_ctx_s iter_ctx_t;
+struct iter_ctx_s {
+ int ncalls;
+ node_t *last_node;
+
+ int ncalls_max;
+ bool forward;
+};
+
+static node_t *
+tree_iterate_filtered_cb(tree_t *tree, node_t *node, void *arg) {
+ iter_ctx_t *ctx = (iter_ctx_t *)arg;
+ ctx->ncalls++;
+ expect_u64_ge(node->specialness, 1,
+ "Should only invoke cb on nodes that pass the filter");
+ if (ctx->last_node != NULL) {
+ if (ctx->forward) {
+ expect_d_lt(node_cmp(ctx->last_node, node), 0,
+ "Incorrect iteration order");
+ } else {
+ expect_d_gt(node_cmp(ctx->last_node, node), 0,
+ "Incorrect iteration order");
+ }
+ }
+ ctx->last_node = node;
+ if (ctx->ncalls == ctx->ncalls_max) {
+ return node;
+ }
+ return NULL;
+}
+
+static int
+qsort_node_cmp(const void *ap, const void *bp) {
+ node_t *a = *(node_t **)ap;
+ node_t *b = *(node_t **)bp;
+ return node_cmp(a, b);
+}
+
+#define UPDATE_TEST_MAX 100
+static void
+check_consistency(tree_t *tree, node_t nodes[UPDATE_TEST_MAX], int nnodes) {
+ uint64_t specialness = 1;
+
+ bool empty;
+ bool real_empty = true;
+ node_t *first;
+ node_t *real_first = NULL;
+ node_t *last;
+ node_t *real_last = NULL;
+ for (int i = 0; i < nnodes; i++) {
+ if (nodes[i].specialness >= specialness) {
+ real_empty = false;
+ if (real_first == NULL
+ || node_cmp(&nodes[i], real_first) < 0) {
+ real_first = &nodes[i];
+ }
+ if (real_last == NULL
+ || node_cmp(&nodes[i], real_last) > 0) {
+ real_last = &nodes[i];
+ }
+ }
+ }
+
+ empty = tree_empty_filtered(tree, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_b_eq(real_empty, empty, "");
+
+ first = tree_first_filtered(tree, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_ptr_eq(real_first, first, "");
+
+ last = tree_last_filtered(tree, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_ptr_eq(real_last, last, "");
+
+ for (int i = 0; i < nnodes; i++) {
+ node_t *next_filtered;
+ node_t *real_next_filtered = NULL;
+ node_t *prev_filtered;
+ node_t *real_prev_filtered = NULL;
+ for (int j = 0; j < nnodes; j++) {
+ if (nodes[j].specialness < specialness) {
+ continue;
+ }
+ if (node_cmp(&nodes[j], &nodes[i]) < 0
+ && (real_prev_filtered == NULL
+ || node_cmp(&nodes[j], real_prev_filtered) > 0)) {
+ real_prev_filtered = &nodes[j];
+ }
+ if (node_cmp(&nodes[j], &nodes[i]) > 0
+ && (real_next_filtered == NULL
+ || node_cmp(&nodes[j], real_next_filtered) < 0)) {
+ real_next_filtered = &nodes[j];
+ }
+ }
+ next_filtered = tree_next_filtered(tree, &nodes[i],
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_next_filtered, next_filtered, "");
+
+ prev_filtered = tree_prev_filtered(tree, &nodes[i],
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_prev_filtered, prev_filtered, "");
+
+ node_t *search_filtered;
+ node_t *real_search_filtered;
+ node_t *nsearch_filtered;
+ node_t *real_nsearch_filtered;
+ node_t *psearch_filtered;
+ node_t *real_psearch_filtered;
+
+ /*
+ * search, nsearch, psearch from a node before nodes[i] in the
+ * ordering.
+ */
+ node_t before;
+ before.magic = NODE_MAGIC;
+ before.key = nodes[i].key - 1;
+ before.allow_duplicates = false;
+ real_search_filtered = NULL;
+ search_filtered = tree_search_filtered(tree, &before,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_search_filtered, search_filtered, "");
+
+ real_nsearch_filtered = (nodes[i].specialness >= specialness ?
+ &nodes[i] : real_next_filtered);
+ nsearch_filtered = tree_nsearch_filtered(tree, &before,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_nsearch_filtered, nsearch_filtered, "");
+
+ real_psearch_filtered = real_prev_filtered;
+ psearch_filtered = tree_psearch_filtered(tree, &before,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_psearch_filtered, psearch_filtered, "");
+
+ /* search, nsearch, psearch from nodes[i] */
+ real_search_filtered = (nodes[i].specialness >= specialness ?
+ &nodes[i] : NULL);
+ search_filtered = tree_search_filtered(tree, &nodes[i],
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_search_filtered, search_filtered, "");
+
+ real_nsearch_filtered = (nodes[i].specialness >= specialness ?
+ &nodes[i] : real_next_filtered);
+ nsearch_filtered = tree_nsearch_filtered(tree, &nodes[i],
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_nsearch_filtered, nsearch_filtered, "");
+
+ real_psearch_filtered = (nodes[i].specialness >= specialness ?
+ &nodes[i] : real_prev_filtered);
+ psearch_filtered = tree_psearch_filtered(tree, &nodes[i],
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_psearch_filtered, psearch_filtered, "");
+
+ /*
+ * search, nsearch, psearch from a node equivalent to but
+ * distinct from nodes[i].
+ */
+ node_t equiv;
+ equiv.magic = NODE_MAGIC;
+ equiv.key = nodes[i].key;
+ equiv.allow_duplicates = true;
+ real_search_filtered = (nodes[i].specialness >= specialness ?
+ &nodes[i] : NULL);
+ search_filtered = tree_search_filtered(tree, &equiv,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_search_filtered, search_filtered, "");
+
+ real_nsearch_filtered = (nodes[i].specialness >= specialness ?
+ &nodes[i] : real_next_filtered);
+ nsearch_filtered = tree_nsearch_filtered(tree, &equiv,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_nsearch_filtered, nsearch_filtered, "");
+
+ real_psearch_filtered = (nodes[i].specialness >= specialness ?
+ &nodes[i] : real_prev_filtered);
+ psearch_filtered = tree_psearch_filtered(tree, &equiv,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_psearch_filtered, psearch_filtered, "");
+
+ /*
+ * search, nsearch, psearch from a node after nodes[i] in the
+ * ordering.
+ */
+ node_t after;
+ after.magic = NODE_MAGIC;
+ after.key = nodes[i].key + 1;
+ after.allow_duplicates = false;
+ real_search_filtered = NULL;
+ search_filtered = tree_search_filtered(tree, &after,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_search_filtered, search_filtered, "");
+
+ real_nsearch_filtered = real_next_filtered;
+ nsearch_filtered = tree_nsearch_filtered(tree, &after,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_nsearch_filtered, nsearch_filtered, "");
+
+ real_psearch_filtered = (nodes[i].specialness >= specialness ?
+ &nodes[i] : real_prev_filtered);
+ psearch_filtered = tree_psearch_filtered(tree, &after,
+ &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_eq(real_psearch_filtered, psearch_filtered, "");
+ }
+
+ /* Filtered iteration test setup. */
+ int nspecial = 0;
+ node_t *sorted_nodes[UPDATE_TEST_MAX];
+ node_t *sorted_filtered_nodes[UPDATE_TEST_MAX];
+ for (int i = 0; i < nnodes; i++) {
+ sorted_nodes[i] = &nodes[i];
+ }
+ qsort(sorted_nodes, nnodes, sizeof(node_t *), &qsort_node_cmp);
+ for (int i = 0; i < nnodes; i++) {
+ sorted_nodes[i]->rank = i;
+ sorted_nodes[i]->filtered_rank = nspecial;
+ if (sorted_nodes[i]->specialness >= 1) {
+ sorted_filtered_nodes[nspecial] = sorted_nodes[i];
+ nspecial++;
+ }
+ }
+
+ node_t *iter_result;
+
+ iter_ctx_t ctx;
+ ctx.ncalls = 0;
+ ctx.last_node = NULL;
+ ctx.ncalls_max = INT_MAX;
+ ctx.forward = true;
+
+ /* Filtered forward iteration from the beginning. */
+ iter_result = tree_iter_filtered(tree, NULL, &tree_iterate_filtered_cb,
+ &ctx, &specialness_filter_node, &specialness_filter_subtree,
+ &specialness);
+ expect_ptr_null(iter_result, "");
+ expect_d_eq(nspecial, ctx.ncalls, "");
+ /* Filtered forward iteration from a starting point. */
+ for (int i = 0; i < nnodes; i++) {
+ ctx.ncalls = 0;
+ ctx.last_node = NULL;
+ iter_result = tree_iter_filtered(tree, &nodes[i],
+ &tree_iterate_filtered_cb, &ctx, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_ptr_null(iter_result, "");
+ expect_d_eq(nspecial - nodes[i].filtered_rank, ctx.ncalls, "");
+ }
+ /* Filtered forward iteration from the beginning, with stopping */
+ for (int i = 0; i < nspecial; i++) {
+ ctx.ncalls = 0;
+ ctx.last_node = NULL;
+ ctx.ncalls_max = i + 1;
+ iter_result = tree_iter_filtered(tree, NULL,
+ &tree_iterate_filtered_cb, &ctx, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_ptr_eq(sorted_filtered_nodes[i], iter_result, "");
+ expect_d_eq(ctx.ncalls, i + 1, "");
+ }
+ /* Filtered forward iteration from a starting point, with stopping. */
+ for (int i = 0; i < nnodes; i++) {
+ for (int j = 0; j < nspecial - nodes[i].filtered_rank; j++) {
+ ctx.ncalls = 0;
+ ctx.last_node = NULL;
+ ctx.ncalls_max = j + 1;
+ iter_result = tree_iter_filtered(tree, &nodes[i],
+ &tree_iterate_filtered_cb, &ctx,
+ &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_d_eq(j + 1, ctx.ncalls, "");
+ expect_ptr_eq(sorted_filtered_nodes[
+ nodes[i].filtered_rank + j], iter_result, "");
+ }
+ }
+
+ /* Backwards iteration. */
+ ctx.ncalls = 0;
+ ctx.last_node = NULL;
+ ctx.ncalls_max = INT_MAX;
+ ctx.forward = false;
+
+ /* Filtered backward iteration from the end. */
+ iter_result = tree_reverse_iter_filtered(tree, NULL,
+ &tree_iterate_filtered_cb, &ctx, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_ptr_null(iter_result, "");
+ expect_d_eq(nspecial, ctx.ncalls, "");
+ /* Filtered backward iteration from a starting point. */
+ for (int i = 0; i < nnodes; i++) {
+ ctx.ncalls = 0;
+ ctx.last_node = NULL;
+ iter_result = tree_reverse_iter_filtered(tree, &nodes[i],
+ &tree_iterate_filtered_cb, &ctx, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_ptr_null(iter_result, "");
+ int surplus_rank = (nodes[i].specialness >= 1 ? 1 : 0);
+ expect_d_eq(nodes[i].filtered_rank + surplus_rank, ctx.ncalls,
+ "");
+ }
+ /* Filtered backward iteration from the end, with stopping */
+ for (int i = 0; i < nspecial; i++) {
+ ctx.ncalls = 0;
+ ctx.last_node = NULL;
+ ctx.ncalls_max = i + 1;
+ iter_result = tree_reverse_iter_filtered(tree, NULL,
+ &tree_iterate_filtered_cb, &ctx, &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_ptr_eq(sorted_filtered_nodes[nspecial - i - 1],
+ iter_result, "");
+ expect_d_eq(ctx.ncalls, i + 1, "");
+ }
+ /* Filtered backward iteration from a starting point, with stopping. */
+ for (int i = 0; i < nnodes; i++) {
+ int surplus_rank = (nodes[i].specialness >= 1 ? 1 : 0);
+ for (int j = 0; j < nodes[i].filtered_rank + surplus_rank;
+ j++) {
+ ctx.ncalls = 0;
+ ctx.last_node = NULL;
+ ctx.ncalls_max = j + 1;
+ iter_result = tree_reverse_iter_filtered(tree,
+ &nodes[i], &tree_iterate_filtered_cb, &ctx,
+ &specialness_filter_node,
+ &specialness_filter_subtree, &specialness);
+ expect_d_eq(j + 1, ctx.ncalls, "");
+ expect_ptr_eq(sorted_filtered_nodes[
+ nodes[i].filtered_rank - j - 1 + surplus_rank],
+ iter_result, "");
+ }
+ }
+}
+
+static void
+do_update_search_test(int nnodes, int ntrees, int nremovals,
+ int nupdates) {
+ node_t nodes[UPDATE_TEST_MAX];
+ assert(nnodes <= UPDATE_TEST_MAX);
+
+ sfmt_t *sfmt = init_gen_rand(12345);
+ for (int i = 0; i < ntrees; i++) {
+ tree_t tree;
+ tree_new(&tree);
+ for (int j = 0; j < nnodes; j++) {
+ nodes[j].magic = NODE_MAGIC;
+ /*
+ * In consistency checking, we increment or decrement a
+ * key and assume that the result is not a key in the
+ * tree. This isn't a *real* concern with 64-bit keys
+ * and a good PRNG, but why not be correct anyways?
+ */
+ nodes[j].key = 2 * gen_rand64(sfmt);
+ nodes[j].specialness = 0;
+ nodes[j].mid_remove = false;
+ nodes[j].allow_duplicates = false;
+ nodes[j].summary_lchild = NULL;
+ nodes[j].summary_rchild = NULL;
+ nodes[j].summary_max_specialness = 0;
+ tree_insert(&tree, &nodes[j]);
+ }
+ for (int j = 0; j < nremovals; j++) {
+ int victim = (int)gen_rand64_range(sfmt, nnodes);
+ if (!nodes[victim].mid_remove) {
+ tree_remove(&tree, &nodes[victim]);
+ nodes[victim].mid_remove = true;
+ }
+ }
+ for (int j = 0; j < nnodes; j++) {
+ if (nodes[j].mid_remove) {
+ nodes[j].mid_remove = false;
+ nodes[j].key = 2 * gen_rand64(sfmt);
+ tree_insert(&tree, &nodes[j]);
+ }
+ }
+ for (int j = 0; j < nupdates; j++) {
+ uint32_t ind = gen_rand32_range(sfmt, nnodes);
+ nodes[ind].specialness = 1 - nodes[ind].specialness;
+ tree_update_summaries(&tree, &nodes[ind]);
+ check_consistency(&tree, nodes, nnodes);
+ }
+ }
+}
+
+TEST_BEGIN(test_rb_update_search) {
+ summarize_always_returns_true = false;
+ do_update_search_test(2, 100, 3, 50);
+ do_update_search_test(5, 100, 3, 50);
+ do_update_search_test(12, 100, 5, 1000);
+ do_update_search_test(100, 1, 50, 500);
+}
+TEST_END
+
+typedef rb_tree(node_t) unsummarized_tree_t;
+rb_gen(static UNUSED, unsummarized_tree_, unsummarized_tree_t, node_t, link,
+ node_cmp);
+
+static node_t *
+unsummarized_tree_iterate_cb(unsummarized_tree_t *tree, node_t *node,
+ void *data) {
+ unsigned *i = (unsigned *)data;
+ (*i)++;
+ return NULL;
+}
+/*
+ * The unsummarized and summarized funtionality is implemented via the same
+ * functions; we don't really need to do much more than test that we can exclude
+ * the filtered functionality without anything breaking.
+ */
+TEST_BEGIN(test_rb_unsummarized) {
+ unsummarized_tree_t tree;
+ unsummarized_tree_new(&tree);
+ unsigned nnodes = 0;
+ unsummarized_tree_iter(&tree, NULL, &unsummarized_tree_iterate_cb,
+ &nnodes);
+ expect_u_eq(0, nnodes, "");
+}
+TEST_END
+
+int
+main(void) {
+ return test_no_reentrancy(
+ test_rb_empty,
+ test_rb_random,
+ test_rb_filter_simple,
+ test_rb_update_search,
+ test_rb_unsummarized);
+}