more regress tests for RB_* and RBT_* macros
authoraisha <aisha@openbsd.org>
Fri, 29 Dec 2023 02:37:39 +0000 (02:37 +0000)
committeraisha <aisha@openbsd.org>
Fri, 29 Dec 2023 02:37:39 +0000 (02:37 +0000)
help, pointers and OK bluhm@

regress/sys/sys/tree/rb/Makefile
regress/sys/sys/tree/rb/rb-test.c
regress/sys/sys/tree/rb/rbt-test.c [new file with mode: 0644]

index 61695ac..1619698 100644 (file)
@@ -1,5 +1,24 @@
-#      $OpenBSD: Makefile,v 1.1 2002/06/11 22:10:22 provos Exp $
+#      $OpenBSD: Makefile,v 1.2 2023/12/29 02:37:39 aisha Exp $
 
-PROG= rb-test
+NAMED_TESTS = random-inserts sequential-inserts \
+       sequential-removes random-removes remove-nfind \
+       remove-pfind node-iterations iteration-macros insert-next \
+       insert-prev benchmarks
+
+REGRESS_TARGETS = all-tests ${NAMED_TESTS}
+
+PROGS = rb-test rbt-test
+
+all-tests: rb-test rbt-test
+       @echo "===== testing RB macros ====="
+       ./rb-test
+       @echo "===== testing RBT macros ====="
+       ./rbt-test
+
+${NAMED_TESTS}: rb-test rbt-test
+       @echo "===== testing RB macros ====="
+       ./rb-test $@
+       @echo "===== testing RBT macros ====="
+       ./rbt-test $@
 
 .include <bsd.regress.mk>
index 409cc22..c26b549 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rb-test.c,v 1.4 2008/04/13 00:22:17 djm Exp $ */
+/*     $OpenBSD: rb-test.c,v 1.5 2023/12/29 02:37:39 aisha Exp $       */
 /*
  * Copyright 2002 Niels Provos <provos@citi.umich.edu>
  * All rights reserved.
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-#include <sys/types.h>
-#include <sys/tree.h>
-#include <unistd.h>
-#include <stdio.h>
+#include <sys/time.h>
+
+#include <assert.h>
 #include <err.h>
+#include <stddef.h>
 #include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+struct timespec start, end, diff, rstart, rend, rdiff, rtot = {0, 0};
+#ifndef timespecsub
+#define        timespecsub(tsp, usp, vsp)                                      \
+       do {                                                            \
+               (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec;          \
+               (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec;       \
+               if ((vsp)->tv_nsec < 0) {                               \
+                       (vsp)->tv_sec--;                                \
+                       (vsp)->tv_nsec += 1000000000L;                  \
+               }                                                       \
+       } while (0)
+#endif
+#ifndef timespecadd
+#define        timespecadd(tsp, usp, vsp)                                      \
+       do {                                                            \
+               (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec;          \
+               (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec;       \
+               if ((vsp)->tv_nsec >= 1000000000L) {                    \
+                       (vsp)->tv_sec++;                                \
+                       (vsp)->tv_nsec -= 1000000000L;                  \
+               }                                                       \
+       } while (0)
+#endif
+
+//#define RB_SMALL
+//#define RB_TEST_RANK
+//#define RB_TEST_DIAGNOSTIC
+//#define _RB_DIAGNOSTIC
+
+#ifdef DOAUGMENT
+#define RB_AUGMENT(elm) tree_augment(elm)
+#endif
+
+#include <sys/tree.h>
+
+#define TDEBUGF(fmt, ...)                                              \
+       fprintf(stderr, "%s:%d:%s(): " fmt "\n",                        \
+        __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 
+
+#ifdef __OpenBSD__
+#define SEED_RANDOM srandom_deterministic
+#else
+#define SEED_RANDOM srandom
+#endif
+
+int ITER=150000;
+int RANK_TEST_ITERATIONS=10000;
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+/* declarations */
+struct node;
+struct tree;
+static int compare(const struct node *, const struct node *);
+static void mix_operations(int *, int, struct node *, int, int, int, int);
+
+#ifdef DOAUGMENT
+static int tree_augment(struct node *);
+#else
+#define tree_augment(x) (0)
+#endif
+
+#ifdef RB_TEST_DIAGNOSTIC
+static void print_helper(const struct node *, int);
+static void print_tree(const struct tree *);
+#else
+#define print_helper(x, y)      do {} while (0)
+#define print_tree(x)     do {} while (0)
+#endif
+
+/* definitions */
 struct node {
-       RB_ENTRY(node) node;
-       int key;
+       RB_ENTRY(node)   node_link;
+       int              key;
+       size_t           height;
+       size_t           size;
 };
 
-RB_HEAD(tree, node) root;
+RB_HEAD(tree, node);
+struct tree root = RB_INITIALIZER(&root);
 
-static int
-compare(struct node *a, struct node *b)
-{
-       if (a->key < b->key) return (-1);
-       else if (a->key > b->key) return (1);
-       return (0);
-}
+RB_PROTOTYPE(tree, node, node_link, compare)
 
-RB_PROTOTYPE(tree, node, node, compare);
+RB_GENERATE(tree, node, node_link, compare)
 
-RB_GENERATE(tree, node, node, compare);
-
-#define ITER 150
-#define MIN 5
-#define MAX 5000
+#ifndef RB_RANK
+#define RB_RANK(x, y)   0
+#endif
+#ifndef _RB_GET_RDIFF
+#define _RB_GET_RDIFF(x, y, z) 0
+#endif
 
 int
 main(int argc, char **argv)
 {
-       struct node *tmp, *ins;
-       int i, max, min;
+       char *test_target = NULL;
+       struct node *tmp, *ins, *nodes;
+       int i, r, rank, *perm, *nums;
+
+       if (argc > 1)
+               test_target = argv[1];
+
+       nodes = calloc((ITER + 5), sizeof(struct node));
+       perm = calloc(ITER, sizeof(int));
+       nums = calloc(ITER, sizeof(int));
+
+       // for determinism
+       SEED_RANDOM(4201);
+
+       TDEBUGF("generating a 'random' permutation");
+       clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+       perm[0] = 0;
+       nums[0] = 0;
+       for(i = 1; i < ITER; i++) {
+               r = random() % i; // arc4random_uniform(i);
+               perm[i] = perm[r];
+               perm[r] = i;
+               nums[i] = i;
+       }
+       /*
+       fprintf(stderr, "{");
+       for(int i = 0; i < ITER; i++) {
+               fprintf(stderr, "%d, ", perm[i]);
+       }
+       fprintf(stderr, "}\n");
+       int nperm[10] = {2, 4, 9, 7, 8, 3, 0, 1, 6, 5};
+       int nperm[6] = {2, 6, 1, 4, 5, 3};
+       int nperm[10] = {10, 3, 7, 8, 6, 1, 9, 2, 5, 4};
+       int nperm[2] = {0, 1};
+
+       int nperm[100] = {
+               54, 47, 31, 35, 40, 73, 29, 66, 15, 45, 9, 71, 51, 32, 28, 62,
+               12, 46, 50, 26, 36, 91, 10, 76, 33, 43, 34, 58, 55, 72, 37, 24,
+               75, 4, 90, 88, 30, 25, 82, 18, 67, 81, 80, 65, 23, 41, 61, 86,
+               20, 99, 59, 14, 79, 21, 68, 27, 1, 7, 94, 44, 89, 64, 96, 2, 49,
+               53, 74, 13, 48, 42, 60, 52, 95, 17, 11, 0, 22, 97, 77, 69, 6,
+               16, 84, 78, 8, 83, 98, 93, 39, 38, 85, 70, 3, 19, 57, 5, 87,
+               92, 63, 56
+       };
+       ITER = 100;
+       for(int i = 0; i < ITER; i++){
+               perm[i] = nperm[i];
+       }
+       */
+
+       clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+       timespecsub(&end, &start, &diff);
+       TDEBUGF("done generating a 'random' permutation in: %llu.%09llu s",
+           (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
 
        RB_INIT(&root);
 
-       for (i = 0; i < ITER; i++) {
+       // testing random inserts
+       // due to codependency between inserts and removals, this also tests
+       // root removals
+       if (test_target == NULL ||
+           strcmp(test_target, "random-inserts") == 0
+       ) {
+               TDEBUGF("starting random insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done random insertions in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+#ifdef DOAUGMENT
+               ins = RB_ROOT(&root);
+               assert(ITER + 1 == ins->size);
+#endif
+
+               TDEBUGF("getting min");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               ins = RB_MIN(tree, &root);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done getting min in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+               assert(0 == ins->key);
+
+               TDEBUGF("getting max");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               ins = RB_MAX(tree, &root);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done getting max in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+               assert(ITER + 5 == ins->key);
+
+               ins = RB_ROOT(&root);
+               TDEBUGF("getting root");
+               assert(RB_REMOVE(tree, &root, ins) == ins);
+
+#ifdef DOAUGMENT
+               assert(ITER == (RB_ROOT(&root))->size);
+#endif
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER; i++) {
+                       tmp = RB_ROOT(&root);
+                       assert(NULL != tmp);
+                       assert(RB_REMOVE(tree, &root, tmp) == tmp);
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               assert(-2 != rank);
+                               print_tree(&root);
+                       }
+#endif
+
+#ifdef DOAUGMENT
+                       if (!(RB_EMPTY(&root)) && (RB_ROOT(&root))->size != ITER - 1 - i)
+                               errx(1, "RB_REMOVE size error");
+#endif
+
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+       // testing insertions in increasing sequential order
+       // removals are done using root removals (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "sequential-inserts") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER + 1; i++) {
+                       tmp = RB_ROOT(&root);
+                       assert(NULL != tmp);
+                       assert(RB_REMOVE(tree, &root, tmp) == tmp);
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+       // testing the RB_FIND function and using it to do removals in
+       // sequential order
+       // insertions are done using sequential insertions (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "sequential-removes") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing find and remove in sequential order");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
                tmp = malloc(sizeof(struct node));
-               if (tmp == NULL) err(1, "malloc");
-               do {
-                       tmp->key = arc4random_uniform(MAX-MIN);
-                       tmp->key += MIN;
-               } while (RB_FIND(tree, &root, tmp) != NULL);
-               if (i == 0)
-                       max = min = tmp->key;
-               else {
-                       if (tmp->key > max)
-                               max = tmp->key;
-                       if (tmp->key < min)
-                               min = tmp->key;
+               for(i = 0; i < ITER; i++) {
+                       tmp->key = i;
+                       ins = RB_FIND(tree, &root, tmp);
+                       assert(NULL != tmp);
+                       assert(RB_REMOVE(tree, &root, ins) == ins);
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
                }
-               if (RB_INSERT(tree, &root, tmp) != NULL)
-                       errx(1, "RB_INSERT failed");
+               free(tmp);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done removals in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               ins = RB_ROOT(&root);
+               if (ins == NULL)
+                       errx(1, "RB_ROOT error");
+               if (RB_REMOVE(tree, &root, ins) != ins)
+                       errx(1, "RB_REMOVE failed");
        }
 
-       ins = RB_MIN(tree, &root);
-       if (ins->key != min)
-               errx(1, "min does not match");
-       tmp = ins;
-       ins = RB_MAX(tree, &root);
-       if (ins->key != max)
-               errx(1, "max does not match");
+       // testing removals in random order
+       // the elements are found using RB_FIND
+       // insertions are done using sequential insertions (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "random-removes") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
 
-       if (RB_REMOVE(tree, &root, tmp) != tmp)
-               errx(1, "RB_REMOVE failed");
 
-       for (i = 0; i < ITER - 1; i++) {
-               tmp = RB_ROOT(&root);
-               if (tmp == NULL)
+               TDEBUGF("doing find and remove in random order");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = malloc(sizeof(struct node));
+               for(i = 0; i < ITER; i++) {
+                       tmp->key = perm[i];
+                       ins = RB_FIND(tree, &root, tmp);
+                       if (ins == NULL) {
+                               errx(1, "RB_FIND %d failed: %d", i, perm[i]);
+                       }
+                       if (RB_REMOVE(tree, &root, ins) == NULL)
+                               errx(1, "RB_REMOVE failed: %d", i);
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               free(tmp);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done removals in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               ins = RB_ROOT(&root);
+               if (ins == NULL)
                        errx(1, "RB_ROOT error");
-               if (RB_REMOVE(tree, &root, tmp) != tmp)
-                       errx(1, "RB_REMOVE error");
+               if (RB_REMOVE(tree, &root, ins) != ins)
+                       errx(1, "RB_REMOVE failed");
+       }
+
+       // testing removals by finding the next largest element
+       // this is testing the RB_NFIND macro
+       // insertions are done using sequential insertions (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "remove-nfind") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing nfind and remove");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = malloc(sizeof(struct node));
+               for(i = 0; i < ITER + 1; i++) {
+                       tmp->key = i;
+                       ins = RB_NFIND(tree, &root, tmp);
+                       if (ins == NULL)
+                               errx(1, "RB_NFIND failed");
+                       if (RB_REMOVE(tree, &root, ins) == NULL)
+                               errx(1, "RB_REMOVE failed: %d", i);
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               free(tmp);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done removals in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+#ifdef RB_PFIND
+       // testing removals by finding the previous element
+       // this is testing the RB_PFIND macro
+       // insertions are done using sequential insertions (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "remove-pfind") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing pfind and remove");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = malloc(sizeof(struct node));
+               for(i = 0; i < ITER + 1; i++) {
+                       tmp->key = ITER + 6;
+                       ins = RB_PFIND(tree, &root, tmp);
+                       if (ins == NULL)
+                               errx(1, "RB_PFIND failed");
+                       if (RB_REMOVE(tree, &root, ins) == NULL)
+                               errx(1, "RB_REMOVE failed: %d", i);
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
                free(tmp);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done removals in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+#endif
+
+       // iterate over the tree using RB_NEXT/RB_PREV
+       // insertions and removals have separate tests
+       if (test_target == NULL ||
+           strcmp(test_target, "node-iterations") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree with RB_NEXT");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = RB_MIN(tree, &root);
+               assert(tmp != NULL);
+               assert(tmp->key == 0);
+               for(i = 1; i < ITER; i++) {
+                       tmp = RB_NEXT(tree, &root, tmp);
+                       assert(tmp != NULL);
+                       assert(tmp->key == i);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree with RB_PREV");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = RB_MAX(tree, &root);
+               assert(tmp != NULL);
+               assert(tmp->key == ITER + 5);
+               for(i = 0; i < ITER; i++) {
+                       tmp = RB_PREV(tree, &root, tmp);
+                       assert(tmp != NULL);
+                       assert(tmp->key == ITER - 1 - i);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER + 1; i++) {
+                       tmp = RB_ROOT(&root);
+                       assert(NULL != tmp);
+                       assert(RB_REMOVE(tree, &root, tmp) == tmp);
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+       // iterate over the tree using RB_FOREACH* macros
+       // the *_SAFE macros are tested by using them to clear the tree
+       // insertions and removals have separate tests
+       if (test_target == NULL ||
+           strcmp(test_target, "iteration-macros") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+#ifdef RB_FOREACH
+               TDEBUGF("iterating over tree with RB_FOREACH");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               i = 0;
+               RB_FOREACH(ins, tree, &root) {
+                       if (i < ITER)
+                               assert(ins->key == i);
+                       else
+                               assert(ins->key == ITER + 5);
+                       i++;
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+#endif
+
+#ifdef RB_FOREACH_REVERSE
+               TDEBUGF("iterating over tree with RB_FOREACH_REVERSE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               i = ITER + 5;
+               RB_FOREACH_REVERSE(ins, tree, &root) {
+                       assert(ins->key == i);
+                       if (i > ITER)
+                               i = ITER - 1;
+                       else
+                               i--;
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+#endif
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER + 1; i++) {
+                       tmp = RB_ROOT(&root);
+                       assert(NULL != tmp);
+                       assert(RB_REMOVE(tree, &root, tmp) == tmp);
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+#ifdef RB_FOREACH_SAFE
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree and clearing with RB_FOREACH_SAFE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               i = 0;
+               RB_FOREACH_SAFE(ins, tree, &root, tmp) {
+                       if (i < ITER)
+                               assert(ins->key == i);
+                       else
+                               assert(ins->key == ITER + 5);
+                       i++;
+                       assert(RB_REMOVE(tree, &root, ins) == ins);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+#endif
+
+#ifdef RB_FOREACH_REVERSE_SAFE
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree and clearing with RB_FOREACH_REVERSE_SAFE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               i = ITER + 5;
+               RB_FOREACH_REVERSE_SAFE(ins, tree, &root, tmp) {
+                       assert(ins->key == i);
+                       if (i > ITER)
+                               i = ITER - 1;
+                       else
+                               i--;
+                       assert(RB_REMOVE(tree, &root, ins) == ins);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+#endif
+       }
+
+#ifdef RB_INSERT_NEXT
+       // testing insertions at specific points in the tree
+       // the insertions are done at the next position in the tree at a give node
+       // this assumes that the location is correct for the given ordering
+       if (test_target == NULL ||
+           strcmp(test_target, "insert-next") == 0
+       ) {
+               TDEBUGF("starting sequential insertions using INSERT_NEXT");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = &(nodes[0]);
+               tmp->size = 1;
+               tmp->height = 1;
+               tmp->key = 0;
+               if (RB_INSERT(tree, &root, tmp) != NULL)
+                       errx(1, "RB_INSERT failed");
+               ins = tmp;
+               for(i = 1; i < ITER; i++) {
+                       tmp = &(nodes[i]);
+                       tmp->size = 1;
+                       tmp->height = 1;
+                       tmp->key = i;
+                       if (RB_INSERT_NEXT(tree, &root, ins, tmp) != NULL)
+                               errx(1, "RB_INSERT failed");
+                       ins = tmp;
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done insertions in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree and clearing with RB_FOREACH_REVERSE_SAFE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               RB_FOREACH_REVERSE_SAFE(ins, tree, &root, tmp) {
+                       assert(RB_REMOVE(tree, &root, ins) == ins);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
        }
+#endif
+
+#ifdef RB_INSERT_PREV
+       // testing insertions at specific points in the tree
+       // the insertions are done at the next position in the tree at a give node
+       // this assumes that the location is correct for the given ordering
+       if (test_target == NULL ||
+           strcmp(test_target, "insert-prev") == 0
+       ) {
+               TDEBUGF("starting sequential insertions using INSERT_PREV");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = &(nodes[ITER]);
+               tmp->size = 1;
+               tmp->height = 1;
+               tmp->key = ITER;
+               if (RB_INSERT(tree, &root, tmp) != NULL)
+                       errx(1, "RB_INSERT failed");
+               ins = tmp;
+               for(i = ITER - 1; i >= 0; i--) {
+                       tmp = &(nodes[i]);
+                       tmp->size = 1;
+                       tmp->height = 1;
+                       tmp->key = i;
+                       if (RB_INSERT_PREV(tree, &root, ins, tmp) != NULL)
+                               errx(1, "RB_INSERT failed");
+                       ins = tmp;
+#ifdef RB_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RB_RANK(tree, RB_ROOT(&root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done insertions in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree and clearing with RB_FOREACH_REVERSE_SAFE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               RB_FOREACH_REVERSE_SAFE(ins, tree, &root, tmp) {
+                       assert(RB_REMOVE(tree, &root, ins) == ins);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+#endif
+
+       if (test_target == NULL ||
+           strcmp(test_target, "benchmarks") == 0
+       ) {
+               TDEBUGF("doing 50%% insertions, 50%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER, ITER / 2, ITER / 2, 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER / 2 + 1; i++) {
+                       tmp = RB_ROOT(&root);
+                       if (tmp == NULL)
+                               errx(1, "RB_ROOT error");
+                       if (RB_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RB_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing 20%% insertions, 80%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER, ITER / 5, 4 * (ITER / 5), 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER / 5 + 1; i++) {
+                       tmp = RB_ROOT(&root);
+                       if (tmp == NULL)
+                               errx(1, "RB_ROOT error");
+                       if (RB_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RB_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing 10%% insertions, 90%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER, ITER / 10, 9 * (ITER / 10), 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER / 10 + 1; i++) {
+                       tmp = RB_ROOT(&root);
+                       if (tmp == NULL)
+                               errx(1, "RB_ROOT error");
+                       if (RB_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RB_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing 5%% insertions, 95%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes,
+               ITER, 5 * (ITER / 100),
+               95 * (ITER / 100), 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < 5 * (ITER / 100) + 1; i++) {
+                       tmp = RB_ROOT(&root);
+                       if (tmp == NULL)
+                               errx(1, "RB_ROOT error");
+                       if (RB_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RB_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
 
-       exit(0);
+               TDEBUGF("doing 2%% insertions, 98%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER,
+               2 * (ITER / 100),
+               98 * (ITER / 100), 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < 2 * (ITER / 100) + 1; i++) {
+                       tmp = RB_ROOT(&root);
+                       if (tmp == NULL)
+                               errx(1, "RB_ROOT error");
+                       if (RB_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RB_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+       free(nodes);
+       free(perm);
+       free(nums);
+
+       return 0;
+}
+
+
+static int
+compare(const struct node *a, const struct node *b)
+{
+       return a->key - b->key;
+}
+
+#ifdef RB_TEST_DIAGNOSTIC
+static void
+print_helper(const struct node *n, int indent)
+{
+       if (RB_RIGHT(n, node_link))
+               print_helper(RB_RIGHT(n, node_link), indent + 4);
+       TDEBUGF("%*s key=%d :: size=%zu :: rank=%d :: rdiff %lu:%lu",
+           indent, "", n->key, n->size, RB_RANK(tree, n),
+           _RB_GET_RDIFF(n, _RB_LDIR, node_link),
+           _RB_GET_RDIFF(n, _RB_RDIR, node_link));
+       if (RB_LEFT(n, node_link))
+               print_helper(RB_LEFT(n, node_link), indent + 4);
+}
+
+static void
+print_tree(const struct tree *t)
+{
+       if (RB_ROOT(t)) print_helper(RB_ROOT(t), 0);
+}
+#endif
+
+#ifdef DOAUGMENT
+static int
+tree_augment(struct node *elm)
+{
+       size_t newsize = 1, newheight = 0;
+       if ((RB_LEFT(elm, node_link))) {
+               newsize += (RB_LEFT(elm, node_link))->size;
+               newheight = MAX((RB_LEFT(elm, node_link))->height, newheight);
+       }
+       if ((RB_RIGHT(elm, node_link))) {
+               newsize += (RB_RIGHT(elm, node_link))->size;
+               newheight = MAX((RB_RIGHT(elm, node_link))->height, newheight);
+       }
+       newheight += 1;
+       if (elm->size != newsize || elm->height != newheight) {
+               elm->size = newsize;
+               elm->height = newheight;
+               return 1;
+       }
+       return 0;
+}
+#endif
+
+
+void
+mix_operations(int *perm, int psize, struct node *nodes, int nsize,
+    int insertions, int reads, int do_reads)
+{
+       int i, rank;
+       struct node *tmp, *ins;
+       struct node it;
+       assert(psize == nsize);
+       assert(insertions + reads <= psize);
+
+       for(i = 0; i < insertions; i++) {
+                //TDEBUGF("iteration %d", i);
+               tmp = &(nodes[i]);
+               if (tmp == NULL) err(1, "malloc");
+               tmp->size = 1;
+               tmp->height = 1;
+               tmp->key = perm[i];
+               //TDEBUGF("inserting %d", tmp->key);
+               if (RB_INSERT(tree, &root, tmp) != NULL)
+                       errx(1, "RB_INSERT failed");
+                print_tree(&root);
+#ifdef DOAUGMENT
+                //TDEBUGF("size = %zu", RB_ROOT(&root)->size);
+                assert(RB_ROOT(&root)->size == i + 1);
+#endif
+
+#ifdef RB_TEST_RANK
+               if (i % RANK_TEST_ITERATIONS == 0) {
+                       rank = RB_RANK(tree, RB_ROOT(&root));
+                       if (rank == -2)
+                               errx(1, "rank error");
+               }
+#endif
+       }
+       tmp = &(nodes[insertions]);
+       tmp->key = ITER + 5;
+       tmp->size = 1;
+       tmp->height = 1;
+       RB_INSERT(tree, &root, tmp);
+       if (do_reads) {
+               for (i = 0; i < insertions; i++) {
+                       it.key = perm[i];
+                       ins = RB_FIND(tree, &root, &it);
+                       if ((ins == NULL) || ins->key != it.key)
+                               errx(1, "RB_FIND failed");
+               }
+               for (i = insertions; i < insertions + reads; i++) {
+                       it.key = perm[i];
+                       ins = RB_NFIND(tree, &root, &it);
+                       if (ins->key < it.key)
+                               errx(1, "RB_NFIND failed");
+               }
+       }
 }
diff --git a/regress/sys/sys/tree/rb/rbt-test.c b/regress/sys/sys/tree/rb/rbt-test.c
new file mode 100644 (file)
index 0000000..006f3d3
--- /dev/null
@@ -0,0 +1,975 @@
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/time.h>
+
+#include <assert.h>
+#include <err.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+struct timespec start, end, diff, rstart, rend, rdiff, rtot = {0, 0};
+#ifndef timespecsub
+#define        timespecsub(tsp, usp, vsp)                                      \
+       do {                                                            \
+               (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec;          \
+               (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec;       \
+               if ((vsp)->tv_nsec < 0) {                               \
+                       (vsp)->tv_sec--;                                \
+                       (vsp)->tv_nsec += 1000000000L;                  \
+               }                                                       \
+       } while (0)
+#endif
+#ifndef timespecadd
+#define        timespecadd(tsp, usp, vsp)                                      \
+       do {                                                            \
+               (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec;          \
+               (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec;       \
+               if ((vsp)->tv_nsec >= 1000000000L) {                    \
+                       (vsp)->tv_sec++;                                \
+                       (vsp)->tv_nsec -= 1000000000L;                  \
+               }                                                       \
+       } while (0)
+#endif
+
+//#define RBT_SMALL
+//#define RBT_TEST_RANK
+//#define RBT_TEST_DIAGNOSTIC
+//#define _RBT_DIAGNOSTIC
+
+#ifdef DOAUGMENT
+#define RBT_AUGMENT(elm) tree_augment(elm)
+#endif
+
+#include <sys/tree.h>
+
+#define TDEBUGF(fmt, ...)                                              \
+       fprintf(stderr, "%s:%d:%s(): " fmt "\n",                        \
+        __FILE__, __LINE__, __func__, ##__VA_ARGS__)
+
+
+#ifdef __OpenBSD__
+#define SEED_RANDOM srandom_deterministic
+#else
+#define SEED_RANDOM srandom
+#endif
+
+int ITER=150000;
+int RANK_TEST_ITERATIONS=10000;
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+/* declarations */
+struct node;
+struct tree;
+static int compare(const struct node *, const struct node *);
+static void mix_operations(int *, int, struct node *, int, int, int, int);
+
+#ifdef DOAUGMENT
+static int tree_augment(struct node *);
+#else
+#define tree_augment(x) (0)
+#endif
+
+#ifdef RBT_TEST_DIAGNOSTIC
+static void print_helper(const struct node *, int);
+static void print_tree(const struct tree *);
+#else
+#define print_helper(x, y)      do {} while (0)
+#define print_tree(x)     do {} while (0)
+#endif
+
+/* definitions */
+struct node {
+       RBT_ENTRY(node)   node_link;
+       int              key;
+       size_t           height;
+       size_t           size;
+};
+
+RBT_HEAD(tree, node);
+struct tree root = RBT_INITIALIZER(&root);
+
+RBT_PROTOTYPE(tree, node, node_link, compare)
+
+RBT_GENERATE(tree, node, node_link, compare);
+
+#ifndef RBT_RANK
+#define RBT_RANK(x, y)   0
+#endif
+#ifndef _RBT_GET_RDIFF
+#define _RBT_GET_RDIFF(x, y, z) 0
+#endif
+
+int
+main(int argc, char **argv)
+{
+       char *test_target = NULL;
+       struct node *tmp, *ins, *nodes;
+       int i, r, rank, *perm, *nums;
+
+       if (argc > 1)
+               test_target = argv[1];
+
+       nodes = calloc((ITER + 5), sizeof(struct node));
+       perm = calloc(ITER, sizeof(int));
+       nums = calloc(ITER, sizeof(int));
+
+       // for determinism
+       SEED_RANDOM(4201);
+
+       TDEBUGF("generating a 'random' permutation");
+       clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+       perm[0] = 0;
+       nums[0] = 0;
+       for(i = 1; i < ITER; i++) {
+               r = random() % i; // arc4random_uniform(i);
+               perm[i] = perm[r];
+               perm[r] = i;
+               nums[i] = i;
+       }
+       /*
+       fprintf(stderr, "{");
+       for(int i = 0; i < ITER; i++) {
+               fprintf(stderr, "%d, ", perm[i]);
+       }
+       fprintf(stderr, "}\n");
+       int nperm[10] = {2, 4, 9, 7, 8, 3, 0, 1, 6, 5};
+       int nperm[6] = {2, 6, 1, 4, 5, 3};
+       int nperm[10] = {10, 3, 7, 8, 6, 1, 9, 2, 5, 4};
+       int nperm[2] = {0, 1};
+
+       int nperm[100] = {
+               54, 47, 31, 35, 40, 73, 29, 66, 15, 45, 9, 71, 51, 32, 28, 62,
+               12, 46, 50, 26, 36, 91, 10, 76, 33, 43, 34, 58, 55, 72, 37, 24,
+               75, 4, 90, 88, 30, 25, 82, 18, 67, 81, 80, 65, 23, 41, 61, 86,
+               20, 99, 59, 14, 79, 21, 68, 27, 1, 7, 94, 44, 89, 64, 96, 2, 49,
+               53, 74, 13, 48, 42, 60, 52, 95, 17, 11, 0, 22, 97, 77, 69, 6,
+               16, 84, 78, 8, 83, 98, 93, 39, 38, 85, 70, 3, 19, 57, 5, 87,
+               92, 63, 56
+       };
+       ITER = 100;
+       for(int i = 0; i < ITER; i++){
+               perm[i] = nperm[i];
+       }
+       */
+
+       clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+       timespecsub(&end, &start, &diff);
+       TDEBUGF("done generating a 'random' permutation in: %llu.%09llu s",
+           (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+       RBT_INIT(tree, &root);
+
+       // testing random inserts
+       // due to codependency between inserts and removals, this also tests
+       // root removals
+       if (test_target == NULL ||
+           strcmp(test_target, "random-inserts") == 0
+       ) {
+               TDEBUGF("starting random insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done random insertions in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+#ifdef DOAUGMENT
+               ins = RBT_ROOT(tree, &root);
+               assert(ITER + 1 == ins->size);
+#endif
+
+               TDEBUGF("getting min");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               ins = RBT_MIN(tree, &root);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done getting min in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+               assert(0 == ins->key);
+
+               TDEBUGF("getting max");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               ins = RBT_MAX(tree, &root);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done getting max in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+               assert(ITER + 5 == ins->key);
+
+               ins = RBT_ROOT(tree, &root);
+               TDEBUGF("getting root");
+               assert(RBT_REMOVE(tree, &root, ins) == ins);
+
+#ifdef DOAUGMENT
+               assert(ITER == (RBT_ROOT(tree, &root))->size);
+#endif
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       assert(NULL != tmp);
+                       assert(RBT_REMOVE(tree, &root, tmp) == tmp);
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               assert(-2 != rank);
+                               print_tree(&root);
+                       }
+#endif
+
+#ifdef DOAUGMENT
+                       if (!(RBT_EMPTY(&root)) && (RBT_ROOT(tree, &root))->size != ITER - 1 - i)
+                               errx(1, "RBT_REMOVE size error");
+#endif
+
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+       // testing insertions in increasing sequential order
+       // removals are done using root removals (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "sequential-inserts") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER + 1; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       assert(NULL != tmp);
+                       assert(RBT_REMOVE(tree, &root, tmp) == tmp);
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+       // testing the RBT_FIND function and using it to do removals in
+       // sequential order
+       // insertions are done using sequential insertions (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "sequential-removes") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing find and remove in sequential order");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = malloc(sizeof(struct node));
+               for(i = 0; i < ITER; i++) {
+                       tmp->key = i;
+                       ins = RBT_FIND(tree, &root, tmp);
+                       assert(NULL != tmp);
+                       assert(RBT_REMOVE(tree, &root, ins) == ins);
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               free(tmp);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done removals in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               ins = RBT_ROOT(tree, &root);
+               if (ins == NULL)
+                       errx(1, "RBT_ROOT error");
+               if (RBT_REMOVE(tree, &root, ins) != ins)
+                       errx(1, "RBT_REMOVE failed");
+       }
+
+       // testing removals in random order
+       // the elements are found using RBT_FIND
+       // insertions are done using sequential insertions (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "random-removes") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+
+               TDEBUGF("doing find and remove in random order");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = malloc(sizeof(struct node));
+               for(i = 0; i < ITER; i++) {
+                       tmp->key = perm[i];
+                       ins = RBT_FIND(tree, &root, tmp);
+                       if (ins == NULL) {
+                               errx(1, "RBT_FIND %d failed: %d", i, perm[i]);
+                       }
+                       if (RBT_REMOVE(tree, &root, ins) == NULL)
+                               errx(1, "RBT_REMOVE failed: %d", i);
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               free(tmp);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done removals in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               ins = RBT_ROOT(tree, &root);
+               if (ins == NULL)
+                       errx(1, "RBT_ROOT error");
+               if (RBT_REMOVE(tree, &root, ins) != ins)
+                       errx(1, "RBT_REMOVE failed");
+       }
+
+       // testing removals by finding the next largest element
+       // this is testing the RBT_NFIND macro
+       // insertions are done using sequential insertions (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "remove-nfind") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing nfind and remove");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = malloc(sizeof(struct node));
+               for(i = 0; i < ITER + 1; i++) {
+                       tmp->key = i;
+                       ins = RBT_NFIND(tree, &root, tmp);
+                       if (ins == NULL)
+                               errx(1, "RBT_NFIND failed");
+                       if (RBT_REMOVE(tree, &root, ins) == NULL)
+                               errx(1, "RBT_REMOVE failed: %d", i);
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               free(tmp);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done removals in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+#ifdef RBT_PFIND
+       // testing removals by finding the previous element
+       // this is testing the RBT_PFIND macro
+       // insertions are done using sequential insertions (separate test)
+       if (test_target == NULL ||
+           strcmp(test_target, "remove-pfind") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing pfind and remove");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = malloc(sizeof(struct node));
+               for(i = 0; i < ITER + 1; i++) {
+                       tmp->key = ITER + 6;
+                       ins = RBT_PFIND(tree, &root, tmp);
+                       if (ins == NULL)
+                               errx(1, "RBT_PFIND failed");
+                       if (RBT_REMOVE(tree, &root, ins) == NULL)
+                               errx(1, "RBT_REMOVE failed: %d", i);
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               free(tmp);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done removals in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+#endif
+
+       // iterate over the tree using RBT_NEXT/RBT_PREV
+       // insertions and removals have separate tests
+       if (test_target == NULL ||
+           strcmp(test_target, "node-iterations") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree with RBT_NEXT");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = RBT_MIN(tree, &root);
+               assert(tmp != NULL);
+               assert(tmp->key == 0);
+               for(i = 1; i < ITER; i++) {
+                       tmp = RBT_NEXT(tree, tmp);
+                       assert(tmp != NULL);
+                       assert(tmp->key == i);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree with RBT_PREV");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = RBT_MAX(tree, &root);
+               assert(tmp != NULL);
+               assert(tmp->key == ITER + 5);
+               for(i = 0; i < ITER; i++) {
+                       tmp = RBT_PREV(tree, tmp);
+                       assert(tmp != NULL);
+                       assert(tmp->key == ITER - 1 - i);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER + 1; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       assert(NULL != tmp);
+                       assert(RBT_REMOVE(tree, &root, tmp) == tmp);
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+       // iterate over the tree using RBT_FOREACH* macros
+       // the *_SAFE macros are tested by using them to clear the tree
+       // insertions and removals have separate tests
+       if (test_target == NULL ||
+           strcmp(test_target, "iteration-macros") == 0
+       ) {
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+#ifdef RBT_FOREACH
+               TDEBUGF("iterating over tree with RBT_FOREACH");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               i = 0;
+               RBT_FOREACH(ins, tree, &root) {
+                       if (i < ITER)
+                               assert(ins->key == i);
+                       else
+                               assert(ins->key == ITER + 5);
+                       i++;
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+#endif
+
+#ifdef RBT_FOREACH_REVERSE
+               TDEBUGF("iterating over tree with RBT_FOREACH_REVERSE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               i = ITER + 5;
+               RBT_FOREACH_REVERSE(ins, tree, &root) {
+                       assert(ins->key == i);
+                       if (i > ITER)
+                               i = ITER - 1;
+                       else
+                               i--;
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+#endif
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER + 1; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       assert(NULL != tmp);
+                       assert(RBT_REMOVE(tree, &root, tmp) == tmp);
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+#ifdef RBT_FOREACH_SAFE
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree and clearing with RBT_FOREACH_SAFE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               i = 0;
+               RBT_FOREACH_SAFE(ins, tree, &root, tmp) {
+                       if (i < ITER)
+                               assert(ins->key == i);
+                       else
+                               assert(ins->key == ITER + 5);
+                       i++;
+                       assert(RBT_REMOVE(tree, &root, ins) == ins);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+#endif
+
+#ifdef RBT_FOREACH_REVERSE_SAFE
+               TDEBUGF("starting sequential insertions");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(nums, ITER, nodes, ITER, ITER, 0, 0);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done sequential insertions in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree and clearing with RBT_FOREACH_REVERSE_SAFE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               i = ITER + 5;
+               RBT_FOREACH_REVERSE_SAFE(ins, tree, &root, tmp) {
+                       assert(ins->key == i);
+                       if (i > ITER)
+                               i = ITER - 1;
+                       else
+                               i--;
+                       assert(RBT_REMOVE(tree, &root, ins) == ins);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+#endif
+       }
+
+#ifdef RBT_INSERT_NEXT
+       // testing insertions at specific points in the tree
+       // the insertions are done at the next position in the tree at a give node
+       // this assumes that the location is correct for the given ordering
+       if (test_target == NULL ||
+           strcmp(test_target, "insert-next") == 0
+       ) {
+               TDEBUGF("starting sequential insertions using INSERT_NEXT");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = &(nodes[0]);
+               tmp->size = 1;
+               tmp->height = 1;
+               tmp->key = 0;
+               if (RBT_INSERT(tree, &root, tmp) != NULL)
+                       errx(1, "RBT_INSERT failed");
+               ins = tmp;
+               for(i = 1; i < ITER; i++) {
+                       tmp = &(nodes[i]);
+                       tmp->size = 1;
+                       tmp->height = 1;
+                       tmp->key = i;
+                       if (RBT_INSERT_NEXT(tree, &root, ins, tmp) != NULL)
+                               errx(1, "RBT_INSERT failed");
+                       ins = tmp;
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done insertions in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree and clearing with RBT_FOREACH_REVERSE_SAFE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               RBT_FOREACH_REVERSE_SAFE(ins, tree, &root, tmp) {
+                       assert(RBT_REMOVE(tree, &root, ins) == ins);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+#endif
+
+#ifdef RBT_INSERT_PREV
+       // testing insertions at specific points in the tree
+       // the insertions are done at the next position in the tree at a give node
+       // this assumes that the location is correct for the given ordering
+       if (test_target == NULL ||
+           strcmp(test_target, "insert-prev") == 0
+       ) {
+               TDEBUGF("starting sequential insertions using INSERT_PREV");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               tmp = &(nodes[ITER]);
+               tmp->size = 1;
+               tmp->height = 1;
+               tmp->key = ITER;
+               if (RBT_INSERT(tree, &root, tmp) != NULL)
+                       errx(1, "RBT_INSERT failed");
+               ins = tmp;
+               for(i = ITER - 1; i >= 0; i--) {
+                       tmp = &(nodes[i]);
+                       tmp->size = 1;
+                       tmp->height = 1;
+                       tmp->key = i;
+                       if (RBT_INSERT_PREV(tree, &root, ins, tmp) != NULL)
+                               errx(1, "RBT_INSERT failed");
+                       ins = tmp;
+#ifdef RBT_TEST_RANK
+                       if (i % RANK_TEST_ITERATIONS == 0) {
+                               rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                               if (rank == -2)
+                                       errx(1, "rank error");
+                       }
+#endif
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done insertions in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("iterating over tree and clearing with RBT_FOREACH_REVERSE_SAFE");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               RBT_FOREACH_REVERSE_SAFE(ins, tree, &root, tmp) {
+                       assert(RBT_REMOVE(tree, &root, ins) == ins);
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done iterations in %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+#endif
+
+       if (test_target == NULL ||
+           strcmp(test_target, "benchmarks") == 0
+       ) {
+               TDEBUGF("doing 50%% insertions, 50%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER, ITER / 2, ITER / 2, 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER / 2 + 1; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       if (tmp == NULL)
+                               errx(1, "RBT_ROOT error");
+                       if (RBT_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RBT_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing 20%% insertions, 80%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER, ITER / 5, 4 * (ITER / 5), 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER / 5 + 1; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       if (tmp == NULL)
+                               errx(1, "RBT_ROOT error");
+                       if (RBT_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RBT_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing 10%% insertions, 90%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER, ITER / 10, 9 * (ITER / 10), 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < ITER / 10 + 1; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       if (tmp == NULL)
+                               errx(1, "RBT_ROOT error");
+                       if (RBT_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RBT_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing 5%% insertions, 95%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes,
+               ITER, 5 * (ITER / 100),
+               95 * (ITER / 100), 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < 5 * (ITER / 100) + 1; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       if (tmp == NULL)
+                               errx(1, "RBT_ROOT error");
+                       if (RBT_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RBT_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing 2%% insertions, 98%% lookups");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               mix_operations(perm, ITER, nodes, ITER,
+               2 * (ITER / 100),
+               98 * (ITER / 100), 1);
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done operations in: %lld.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+
+               TDEBUGF("doing root removals");
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+               for (i = 0; i < 2 * (ITER / 100) + 1; i++) {
+                       tmp = RBT_ROOT(tree, &root);
+                       if (tmp == NULL)
+                               errx(1, "RBT_ROOT error");
+                       if (RBT_REMOVE(tree, &root, tmp) != tmp)
+                               errx(1, "RBT_REMOVE error");
+               }
+               clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+               timespecsub(&end, &start, &diff);
+               TDEBUGF("done root removals in: %llu.%09llu s",
+               (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_nsec);
+       }
+
+       free(nodes);
+       free(perm);
+       free(nums);
+
+       return 0;
+}
+
+
+static int
+compare(const struct node *a, const struct node *b)
+{
+       return a->key - b->key;
+}
+
+#ifdef RBT_TEST_DIAGNOSTIC
+static void
+print_helper(const struct node *n, int indent)
+{
+       if (RBT_RIGHT(n, node_link))
+               print_helper(RBT_RIGHT(n, node_link), indent + 4);
+       TDEBUGF("%*s key=%d :: size=%zu :: rank=%d :: rdiff %lu:%lu",
+           indent, "", n->key, n->size, RBT_RANK(tree, n),
+           _RBT_GET_RDIFF(n, _RBT_LDIR, node_link),
+           _RBT_GET_RDIFF(n, _RBT_RDIR, node_link));
+       if (RBT_LEFT(n, node_link))
+               print_helper(RBT_LEFT(n, node_link), indent + 4);
+}
+
+static void
+print_tree(const struct tree *t)
+{
+       if (RBT_ROOT(tree, t)) print_helper(RBT_ROOT(tree, t), 0);
+}
+#endif
+
+#ifdef DOAUGMENT
+static int
+tree_augment(struct node *elm)
+{
+       size_t newsize = 1, newheight = 0;
+       if ((RBT_LEFT(elm, node_link))) {
+               newsize += (RBT_LEFT(elm, node_link))->size;
+               newheight = MAX((RBT_LEFT(elm, node_link))->height, newheight);
+       }
+       if ((RBT_RIGHT(elm, node_link))) {
+               newsize += (RBT_RIGHT(elm, node_link))->size;
+               newheight = MAX((RBT_RIGHT(elm, node_link))->height, newheight);
+       }
+       newheight += 1;
+       if (elm->size != newsize || elm->height != newheight) {
+               elm->size = newsize;
+               elm->height = newheight;
+               return 1;
+       }
+       return 0;
+}
+#endif
+
+
+void
+mix_operations(int *perm, int psize, struct node *nodes, int nsize,
+    int insertions, int reads, int do_reads)
+{
+       int i, rank;
+       struct node *tmp, *ins;
+       struct node it;
+       assert(psize == nsize);
+       assert(insertions + reads <= psize);
+
+       for(i = 0; i < insertions; i++) {
+                //TDEBUGF("iteration %d", i);
+               tmp = &(nodes[i]);
+               if (tmp == NULL) err(1, "malloc");
+               tmp->size = 1;
+               tmp->height = 1;
+               tmp->key = perm[i];
+               //TDEBUGF("inserting %d", tmp->key);
+               if (RBT_INSERT(tree, &root, tmp) != NULL)
+                       errx(1, "RBT_INSERT failed");
+                print_tree(&root);
+#ifdef DOAUGMENT
+                //TDEBUGF("size = %zu", RBT_ROOT(tree, &root)->size);
+                assert(RBT_ROOT(tree, &root)->size == i + 1);
+#endif
+
+#ifdef RBT_TEST_RANK
+               if (i % RANK_TEST_ITERATIONS == 0) {
+                       rank = RBT_RANK(tree, RBT_ROOT(tree, &root));
+                       if (rank == -2)
+                               errx(1, "rank error");
+               }
+#endif
+       }
+       tmp = &(nodes[insertions]);
+       tmp->key = ITER + 5;
+       tmp->size = 1;
+       tmp->height = 1;
+       RBT_INSERT(tree, &root, tmp);
+       if (do_reads) {
+               for (i = 0; i < insertions; i++) {
+                       it.key = perm[i];
+                       ins = RBT_FIND(tree, &root, &it);
+                       if ((ins == NULL) || ins->key != it.key)
+                               errx(1, "RBT_FIND failed");
+               }
+               for (i = insertions; i < insertions + reads; i++) {
+                       it.key = perm[i];
+                       ins = RBT_NFIND(tree, &root, &it);
+                       if (ins->key < it.key)
+                               errx(1, "RBT_NFIND failed");
+               }
+       }
+}