291 lines
9.7 KiB
C
291 lines
9.7 KiB
C
/*
|
|
* Test: ptree.c — AVL balanced BST
|
|
*
|
|
* Tests insert, lookup, delete, and in-order traversal using a simple
|
|
* integer-keyed node type. ptree itself manages no memory; nodes are
|
|
* stack-allocated here so no cleanup is needed.
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "ptree.h"
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Node type */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
typedef struct {
|
|
psync_tree tree;
|
|
int key;
|
|
} inode_t;
|
|
|
|
static int icmp(const psync_tree *a, const psync_tree *b) {
|
|
int ka = ptree_element(a, inode_t, tree)->key;
|
|
int kb = ptree_element(b, inode_t, tree)->key;
|
|
return (ka > kb) - (ka < kb);
|
|
}
|
|
|
|
static void inode_init(inode_t *n, int key) {
|
|
memset(&n->tree, 0, sizeof(n->tree));
|
|
n->key = key;
|
|
}
|
|
|
|
/* BST lookup by key */
|
|
static inode_t *find_key(psync_tree *root, int key) {
|
|
while (root) {
|
|
inode_t *n = ptree_element(root, inode_t, tree);
|
|
if (key < n->key) root = root->left;
|
|
else if (key > n->key) root = root->right;
|
|
else return n;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Test harness */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int passes = 0, failures = 0;
|
|
#define PASS(n) do { printf("PASS: %s\n", n); passes++; } while (0)
|
|
#define FAIL(n, ...) do { printf("FAIL: %s — ", n); printf(__VA_ARGS__); printf("\n"); failures++; } while (0)
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Tests */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Single node: first == last == the node, next/prev return NULL */
|
|
static void test_single_node(void) {
|
|
inode_t n;
|
|
psync_tree *root = NULL;
|
|
inode_init(&n, 42);
|
|
ptree_add(&root, &n.tree, icmp);
|
|
if (root == NULL)
|
|
{ FAIL("single insert: root non-null", "root is NULL"); return; }
|
|
psync_tree *f = ptree_get_first(root);
|
|
psync_tree *l = ptree_get_last(root);
|
|
if (f != &n.tree || l != &n.tree)
|
|
FAIL("single: first == last == node", "first=%p last=%p node=%p",
|
|
(void*)f, (void*)l, (void*)&n.tree);
|
|
else if (ptree_get_next(f) != NULL)
|
|
FAIL("single: next of only node is NULL", "got non-null");
|
|
else if (ptree_get_prev(l) != NULL)
|
|
FAIL("single: prev of only node is NULL", "got non-null");
|
|
else
|
|
PASS("single node insert/first/last/next/prev");
|
|
}
|
|
|
|
/* In-order traversal gives keys in ascending order for any insert order */
|
|
static void test_traversal_sorted(void) {
|
|
int keys[] = { 5, 3, 8, 1, 4, 7, 9, 2 };
|
|
int n = (int)(sizeof(keys) / sizeof(keys[0]));
|
|
inode_t nodes[8];
|
|
psync_tree *root = NULL;
|
|
int i;
|
|
for (i = 0; i < n; i++) {
|
|
inode_init(&nodes[i], keys[i]);
|
|
ptree_add(&root, &nodes[i].tree, icmp);
|
|
}
|
|
/* collect traversal */
|
|
psync_tree *tr = ptree_get_first(root);
|
|
int prev_key = -1, count = 0, ok = 1;
|
|
while (tr) {
|
|
int k = ptree_element(tr, inode_t, tree)->key;
|
|
if (k <= prev_key) { ok = 0; break; }
|
|
prev_key = k;
|
|
count++;
|
|
tr = ptree_get_next(tr);
|
|
}
|
|
if (!ok)
|
|
FAIL("traversal sorted", "out-of-order key %d after %d", prev_key, prev_key);
|
|
else if (count != n)
|
|
FAIL("traversal sorted: count", "expected %d, got %d", n, count);
|
|
else
|
|
PASS("traversal in sorted order after arbitrary inserts");
|
|
}
|
|
|
|
/* Reverse-order insert stresses AVL rebalancing */
|
|
static void test_reverse_insert_traversal(void) {
|
|
int n = 7;
|
|
inode_t nodes[7];
|
|
psync_tree *root = NULL;
|
|
int i;
|
|
for (i = n; i >= 1; i--) {
|
|
inode_init(&nodes[i-1], i);
|
|
ptree_add(&root, &nodes[i-1].tree, icmp);
|
|
}
|
|
psync_tree *tr = ptree_get_first(root);
|
|
int prev = 0, count = 0, ok = 1;
|
|
while (tr) {
|
|
int k = ptree_element(tr, inode_t, tree)->key;
|
|
if (k != prev + 1) { ok = 0; break; }
|
|
prev = k;
|
|
count++;
|
|
tr = ptree_get_next(tr);
|
|
}
|
|
if (!ok || count != n)
|
|
FAIL("reverse insert traversal", "ok=%d count=%d expected %d", ok, count, n);
|
|
else
|
|
PASS("reverse insert: AVL rebalanced, traversal still sorted");
|
|
}
|
|
|
|
/* Lookup by key finds the right node (or NULL for missing) */
|
|
static void test_lookup(void) {
|
|
int keys[] = { 10, 5, 15, 3, 7 };
|
|
int n = (int)(sizeof(keys) / sizeof(keys[0]));
|
|
inode_t nodes[5];
|
|
psync_tree *root = NULL;
|
|
int i;
|
|
for (i = 0; i < n; i++) {
|
|
inode_init(&nodes[i], keys[i]);
|
|
ptree_add(&root, &nodes[i].tree, icmp);
|
|
}
|
|
int ok = 1;
|
|
for (i = 0; i < n; i++) {
|
|
inode_t *found = find_key(root, keys[i]);
|
|
if (!found || found->key != keys[i]) { ok = 0; break; }
|
|
}
|
|
if (!ok)
|
|
FAIL("lookup: existing keys", "key not found");
|
|
else if (find_key(root, 99) != NULL)
|
|
FAIL("lookup: absent key returns NULL", "got non-null for key 99");
|
|
else
|
|
PASS("lookup: finds all inserted keys, NULL for missing");
|
|
}
|
|
|
|
/* Delete a leaf node */
|
|
static void test_delete_leaf(void) {
|
|
int keys[] = { 5, 3, 7 };
|
|
inode_t nodes[3];
|
|
psync_tree *root = NULL;
|
|
int i;
|
|
for (i = 0; i < 3; i++) {
|
|
inode_init(&nodes[i], keys[i]);
|
|
ptree_add(&root, &nodes[i].tree, icmp);
|
|
}
|
|
/* delete leaf (key=3) */
|
|
ptree_del(&root, &nodes[1].tree);
|
|
if (find_key(root, 3) != NULL)
|
|
FAIL("delete leaf: key gone", "key 3 still found");
|
|
else if (find_key(root, 5) == NULL || find_key(root, 7) == NULL)
|
|
FAIL("delete leaf: others remain", "5 or 7 missing");
|
|
else {
|
|
/* traversal gives 5, 7 */
|
|
psync_tree *tr = ptree_get_first(root);
|
|
int a = ptree_element(tr, inode_t, tree)->key;
|
|
tr = ptree_get_next(tr);
|
|
int b = ptree_element(tr, inode_t, tree)->key;
|
|
tr = ptree_get_next(tr);
|
|
if (a == 5 && b == 7 && tr == NULL)
|
|
PASS("delete leaf: correct traversal afterward");
|
|
else
|
|
FAIL("delete leaf: traversal wrong", "a=%d b=%d next=%p", a, b, (void*)tr);
|
|
}
|
|
}
|
|
|
|
/* Delete root when root has two children */
|
|
static void test_delete_root(void) {
|
|
inode_t nodes[5];
|
|
psync_tree *root = NULL;
|
|
int keys[] = { 10, 5, 15, 3, 8 };
|
|
int n = 5, i;
|
|
for (i = 0; i < n; i++) {
|
|
inode_init(&nodes[i], keys[i]);
|
|
ptree_add(&root, &nodes[i].tree, icmp);
|
|
}
|
|
inode_t *old_root = ptree_element(root, inode_t, tree);
|
|
ptree_del(&root, &old_root->tree);
|
|
/* old root key must be absent */
|
|
if (find_key(root, old_root->key) != NULL)
|
|
{ FAIL("delete root: root key gone", "still present"); return; }
|
|
/* remaining keys must all be present */
|
|
int missing = 0;
|
|
for (i = 0; i < n; i++)
|
|
if (keys[i] != old_root->key && find_key(root, keys[i]) == NULL)
|
|
{ missing = keys[i]; break; }
|
|
if (missing)
|
|
FAIL("delete root: remaining keys", "key %d missing", missing);
|
|
else {
|
|
/* traversal still sorted */
|
|
psync_tree *tr = ptree_get_first(root);
|
|
int prev = -1, cnt = 0, ok = 1;
|
|
while (tr) {
|
|
int k = ptree_element(tr, inode_t, tree)->key;
|
|
if (k <= prev) { ok = 0; break; }
|
|
prev = k; cnt++;
|
|
tr = ptree_get_next(tr);
|
|
}
|
|
if (!ok || cnt != n - 1)
|
|
FAIL("delete root: traversal", "ok=%d cnt=%d expected %d", ok, cnt, n-1);
|
|
else
|
|
PASS("delete root: correct traversal after root deletion");
|
|
}
|
|
}
|
|
|
|
/* Delete all nodes one by one; tree must be empty at the end */
|
|
static void test_delete_all(void) {
|
|
int keys[] = { 4, 2, 6, 1, 3, 5, 7 };
|
|
int n = (int)(sizeof(keys) / sizeof(keys[0]));
|
|
inode_t nodes[7];
|
|
psync_tree *root = NULL;
|
|
int i;
|
|
for (i = 0; i < n; i++) {
|
|
inode_init(&nodes[i], keys[i]);
|
|
ptree_add(&root, &nodes[i].tree, icmp);
|
|
}
|
|
for (i = 0; i < n; i++) {
|
|
inode_t *nd = find_key(root, keys[i]);
|
|
if (!nd) { FAIL("delete all: find before delete", "key %d missing", keys[i]); return; }
|
|
ptree_del(&root, &nd->tree);
|
|
}
|
|
if (root != NULL)
|
|
FAIL("delete all: root is NULL after all deletes", "root=%p", (void*)root);
|
|
else if (ptree_get_first(root) != NULL)
|
|
FAIL("delete all: first is NULL", "non-null");
|
|
else
|
|
PASS("delete all: tree empty after deleting all nodes");
|
|
}
|
|
|
|
/* ptree_for_each visits every node exactly once */
|
|
static void test_for_each_macro(void) {
|
|
int keys[] = { 9, 2, 5, 1, 8, 4 };
|
|
int n = (int)(sizeof(keys) / sizeof(keys[0]));
|
|
inode_t nodes[6];
|
|
psync_tree *root = NULL;
|
|
int i;
|
|
for (i = 0; i < n; i++) {
|
|
inode_init(&nodes[i], keys[i]);
|
|
ptree_add(&root, &nodes[i].tree, icmp);
|
|
}
|
|
int seen[6] = {0};
|
|
psync_tree *tr;
|
|
ptree_for_each(tr, root) {
|
|
inode_t *nd = ptree_element(tr, inode_t, tree);
|
|
for (i = 0; i < n; i++)
|
|
if (keys[i] == nd->key) { seen[i]++; break; }
|
|
}
|
|
int ok = 1;
|
|
for (i = 0; i < n; i++)
|
|
if (seen[i] != 1) { ok = 0; break; }
|
|
if (ok)
|
|
PASS("ptree_for_each visits every node exactly once");
|
|
else
|
|
FAIL("ptree_for_each", "some node visited wrong number of times");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
int main(void) {
|
|
test_single_node();
|
|
test_traversal_sorted();
|
|
test_reverse_insert_traversal();
|
|
test_lookup();
|
|
test_delete_leaf();
|
|
test_delete_root();
|
|
test_delete_all();
|
|
test_for_each_macro();
|
|
|
|
printf("\n%d passed, %d failed\n", passes, failures);
|
|
return failures ? 1 : 0;
|
|
}
|