1*22649d4dSMartin Matuska // SPDX-License-Identifier: CDDL-1.0
2*22649d4dSMartin Matuska /*
3*22649d4dSMartin Matuska * This file and its contents are supplied under the terms of the
4*22649d4dSMartin Matuska * Common Development and Distribution License ("CDDL"), version 1.0.
5*22649d4dSMartin Matuska * You may only use this file in accordance with the terms of version
6*22649d4dSMartin Matuska * 1.0 of the CDDL.
7*22649d4dSMartin Matuska *
8*22649d4dSMartin Matuska * A full copy of the text of the CDDL should have accompanied this
9*22649d4dSMartin Matuska * source. A copy of the CDDL is also available via the Internet at
10*22649d4dSMartin Matuska * https://opensource.org/license/CDDL-1.0.
11*22649d4dSMartin Matuska */
12*22649d4dSMartin Matuska
13*22649d4dSMartin Matuska /*
14*22649d4dSMartin Matuska * Copyright (c) 2026, Christos Longros.
15*22649d4dSMartin Matuska */
16*22649d4dSMartin Matuska
17*22649d4dSMartin Matuska #include <stddef.h>
18*22649d4dSMartin Matuska #include <stdlib.h>
19*22649d4dSMartin Matuska #include <sys/types.h>
20*22649d4dSMartin Matuska #include <sys/sysmacros.h>
21*22649d4dSMartin Matuska #include <sys/avl.h>
22*22649d4dSMartin Matuska #include <sys/btree.h>
23*22649d4dSMartin Matuska
24*22649d4dSMartin Matuska #include "unit.h"
25*22649d4dSMartin Matuska
26*22649d4dSMartin Matuska #define DRAIN_COUNT (64 * 1024)
27*22649d4dSMartin Matuska
28*22649d4dSMartin Matuska /* ========== */
29*22649d4dSMartin Matuska
30*22649d4dSMartin Matuska /*
31*22649d4dSMartin Matuska * The B-Tree stores arbitrary sortable data; these tests use plain uint64_t
32*22649d4dSMartin Matuska * values. Elements are kept in sorted order, so a comparison function must
33*22649d4dSMartin Matuska * return -1, 0, or +1 for less-than, equal, and greater-than.
34*22649d4dSMartin Matuska */
35*22649d4dSMartin Matuska static int
u64_compare(const void * a,const void * b)36*22649d4dSMartin Matuska u64_compare(const void *a, const void *b)
37*22649d4dSMartin Matuska {
38*22649d4dSMartin Matuska const uint64_t x = *(const uint64_t *)a;
39*22649d4dSMartin Matuska const uint64_t y = *(const uint64_t *)b;
40*22649d4dSMartin Matuska
41*22649d4dSMartin Matuska return (TREE_CMP(x, y));
42*22649d4dSMartin Matuska }
43*22649d4dSMartin Matuska
44*22649d4dSMartin Matuska /*
45*22649d4dSMartin Matuska * Create a tree of uint64_t values.
46*22649d4dSMartin Matuska */
47*22649d4dSMartin Matuska static void
btree_create_u64(zfs_btree_t * bt)48*22649d4dSMartin Matuska btree_create_u64(zfs_btree_t *bt)
49*22649d4dSMartin Matuska {
50*22649d4dSMartin Matuska zfs_btree_create(bt, u64_compare, NULL, sizeof (uint64_t));
51*22649d4dSMartin Matuska }
52*22649d4dSMartin Matuska
53*22649d4dSMartin Matuska /*
54*22649d4dSMartin Matuska * Cross-check the B-Tree against an AVL tree holding the same
55*22649d4dSMartin Matuska * values, used as reference.
56*22649d4dSMartin Matuska */
57*22649d4dSMartin Matuska typedef struct int_node {
58*22649d4dSMartin Matuska avl_node_t node;
59*22649d4dSMartin Matuska uint64_t data;
60*22649d4dSMartin Matuska } int_node_t;
61*22649d4dSMartin Matuska
62*22649d4dSMartin Matuska static int
avl_u64_compare(const void * v1,const void * v2)63*22649d4dSMartin Matuska avl_u64_compare(const void *v1, const void *v2)
64*22649d4dSMartin Matuska {
65*22649d4dSMartin Matuska const int_node_t *n1 = v1;
66*22649d4dSMartin Matuska const int_node_t *n2 = v2;
67*22649d4dSMartin Matuska
68*22649d4dSMartin Matuska return (TREE_CMP(n1->data, n2->data));
69*22649d4dSMartin Matuska }
70*22649d4dSMartin Matuska
71*22649d4dSMartin Matuska /* ========== */
72*22649d4dSMartin Matuska
73*22649d4dSMartin Matuska /* A new tree is empty: no nodes, and nothing to walk. */
74*22649d4dSMartin Matuska static MunitResult
test_btree_empty(const MunitParameter params[],void * data)75*22649d4dSMartin Matuska test_btree_empty(const MunitParameter params[], void *data)
76*22649d4dSMartin Matuska {
77*22649d4dSMartin Matuska (void) params, (void) data;
78*22649d4dSMartin Matuska
79*22649d4dSMartin Matuska zfs_btree_t bt;
80*22649d4dSMartin Matuska zfs_btree_index_t idx;
81*22649d4dSMartin Matuska
82*22649d4dSMartin Matuska btree_create_u64(&bt);
83*22649d4dSMartin Matuska unit_zero(zfs_btree_numnodes(&bt));
84*22649d4dSMartin Matuska unit_true(zfs_btree_first(&bt, &idx) == NULL);
85*22649d4dSMartin Matuska zfs_btree_clear(&bt);
86*22649d4dSMartin Matuska zfs_btree_destroy(&bt);
87*22649d4dSMartin Matuska
88*22649d4dSMartin Matuska return (MUNIT_OK);
89*22649d4dSMartin Matuska }
90*22649d4dSMartin Matuska
91*22649d4dSMartin Matuska /* Added values can be found; the count tracks them; absent values are not. */
92*22649d4dSMartin Matuska static MunitResult
test_btree_add_find(const MunitParameter params[],void * data)93*22649d4dSMartin Matuska test_btree_add_find(const MunitParameter params[], void *data)
94*22649d4dSMartin Matuska {
95*22649d4dSMartin Matuska (void) params, (void) data;
96*22649d4dSMartin Matuska
97*22649d4dSMartin Matuska zfs_btree_t bt;
98*22649d4dSMartin Matuska zfs_btree_index_t idx;
99*22649d4dSMartin Matuska btree_create_u64(&bt);
100*22649d4dSMartin Matuska
101*22649d4dSMartin Matuska uint64_t vals[] = { 50, 10, 30 };
102*22649d4dSMartin Matuska for (size_t i = 0; i < ARRAY_SIZE(vals); i++)
103*22649d4dSMartin Matuska zfs_btree_add(&bt, &vals[i]);
104*22649d4dSMartin Matuska
105*22649d4dSMartin Matuska /* The tree now holds exactly the values we added. */
106*22649d4dSMartin Matuska unit_eq(zfs_btree_numnodes(&bt), ARRAY_SIZE(vals));
107*22649d4dSMartin Matuska
108*22649d4dSMartin Matuska /* Each value is found; find() returns a pointer to the stored copy. */
109*22649d4dSMartin Matuska for (size_t i = 0; i < ARRAY_SIZE(vals); i++) {
110*22649d4dSMartin Matuska uint64_t *found = zfs_btree_find(&bt, &vals[i], &idx);
111*22649d4dSMartin Matuska unit_true(found != NULL);
112*22649d4dSMartin Matuska unit_eq(*found, vals[i]);
113*22649d4dSMartin Matuska }
114*22649d4dSMartin Matuska
115*22649d4dSMartin Matuska /* A value that was never added is not in the tree. */
116*22649d4dSMartin Matuska uint64_t absent = 99;
117*22649d4dSMartin Matuska unit_true(zfs_btree_find(&bt, &absent, &idx) == NULL);
118*22649d4dSMartin Matuska
119*22649d4dSMartin Matuska zfs_btree_clear(&bt);
120*22649d4dSMartin Matuska zfs_btree_destroy(&bt);
121*22649d4dSMartin Matuska return (MUNIT_OK);
122*22649d4dSMartin Matuska }
123*22649d4dSMartin Matuska
124*22649d4dSMartin Matuska /* Removing a value drops it from the tree and lowers the count. */
125*22649d4dSMartin Matuska static MunitResult
test_btree_remove(const MunitParameter params[],void * data)126*22649d4dSMartin Matuska test_btree_remove(const MunitParameter params[], void *data)
127*22649d4dSMartin Matuska {
128*22649d4dSMartin Matuska (void) params, (void) data;
129*22649d4dSMartin Matuska
130*22649d4dSMartin Matuska zfs_btree_t bt;
131*22649d4dSMartin Matuska zfs_btree_index_t idx;
132*22649d4dSMartin Matuska btree_create_u64(&bt);
133*22649d4dSMartin Matuska
134*22649d4dSMartin Matuska uint64_t vals[] = { 10, 20, 30 };
135*22649d4dSMartin Matuska for (size_t i = 0; i < ARRAY_SIZE(vals); i++)
136*22649d4dSMartin Matuska zfs_btree_add(&bt, &vals[i]);
137*22649d4dSMartin Matuska
138*22649d4dSMartin Matuska uint64_t gone = 20;
139*22649d4dSMartin Matuska zfs_btree_remove(&bt, &gone);
140*22649d4dSMartin Matuska
141*22649d4dSMartin Matuska unit_eq(zfs_btree_numnodes(&bt), 2);
142*22649d4dSMartin Matuska unit_true(zfs_btree_find(&bt, &gone, &idx) == NULL);
143*22649d4dSMartin Matuska
144*22649d4dSMartin Matuska /* The values we kept are still present. */
145*22649d4dSMartin Matuska uint64_t keep1 = 10, keep2 = 30;
146*22649d4dSMartin Matuska unit_true(zfs_btree_find(&bt, &keep1, &idx) != NULL);
147*22649d4dSMartin Matuska unit_true(zfs_btree_find(&bt, &keep2, &idx) != NULL);
148*22649d4dSMartin Matuska
149*22649d4dSMartin Matuska zfs_btree_clear(&bt);
150*22649d4dSMartin Matuska zfs_btree_destroy(&bt);
151*22649d4dSMartin Matuska return (MUNIT_OK);
152*22649d4dSMartin Matuska }
153*22649d4dSMartin Matuska
154*22649d4dSMartin Matuska /* Values inserted out of order are walked back in ascending order. */
155*22649d4dSMartin Matuska static MunitResult
test_btree_walk(const MunitParameter params[],void * data)156*22649d4dSMartin Matuska test_btree_walk(const MunitParameter params[], void *data)
157*22649d4dSMartin Matuska {
158*22649d4dSMartin Matuska (void) params, (void) data;
159*22649d4dSMartin Matuska
160*22649d4dSMartin Matuska zfs_btree_t bt;
161*22649d4dSMartin Matuska zfs_btree_index_t idx;
162*22649d4dSMartin Matuska btree_create_u64(&bt);
163*22649d4dSMartin Matuska
164*22649d4dSMartin Matuska uint64_t vals[] = { 50, 10, 40, 20, 30 };
165*22649d4dSMartin Matuska for (size_t i = 0; i < ARRAY_SIZE(vals); i++)
166*22649d4dSMartin Matuska zfs_btree_add(&bt, &vals[i]);
167*22649d4dSMartin Matuska
168*22649d4dSMartin Matuska /* first()/next() yield the elements smallest-to-largest. */
169*22649d4dSMartin Matuska uint64_t prev = 0;
170*22649d4dSMartin Matuska uint64_t count = 0;
171*22649d4dSMartin Matuska for (uint64_t *p = zfs_btree_first(&bt, &idx); p != NULL;
172*22649d4dSMartin Matuska p = zfs_btree_next(&bt, &idx, &idx)) {
173*22649d4dSMartin Matuska unit_gt(*p, prev); /* strictly increasing */
174*22649d4dSMartin Matuska prev = *p;
175*22649d4dSMartin Matuska count++;
176*22649d4dSMartin Matuska }
177*22649d4dSMartin Matuska unit_eq(count, ARRAY_SIZE(vals));
178*22649d4dSMartin Matuska
179*22649d4dSMartin Matuska zfs_btree_clear(&bt);
180*22649d4dSMartin Matuska zfs_btree_destroy(&bt);
181*22649d4dSMartin Matuska return (MUNIT_OK);
182*22649d4dSMartin Matuska }
183*22649d4dSMartin Matuska
184*22649d4dSMartin Matuska /* Verify that zfs_btree_find() works correctly when passed a NULL index. */
185*22649d4dSMartin Matuska static MunitResult
test_btree_find_without_index(const MunitParameter params[],void * data)186*22649d4dSMartin Matuska test_btree_find_without_index(const MunitParameter params[], void *data)
187*22649d4dSMartin Matuska {
188*22649d4dSMartin Matuska (void) params, (void) data;
189*22649d4dSMartin Matuska
190*22649d4dSMartin Matuska zfs_btree_t bt;
191*22649d4dSMartin Matuska btree_create_u64(&bt);
192*22649d4dSMartin Matuska
193*22649d4dSMartin Matuska uint64_t i = 12345;
194*22649d4dSMartin Matuska zfs_btree_add(&bt, &i);
195*22649d4dSMartin Matuska
196*22649d4dSMartin Matuska uint64_t *p = zfs_btree_find(&bt, &i, NULL);
197*22649d4dSMartin Matuska unit_true(p != NULL);
198*22649d4dSMartin Matuska unit_eq(*p, i);
199*22649d4dSMartin Matuska
200*22649d4dSMartin Matuska uint64_t absent = i + 1;
201*22649d4dSMartin Matuska unit_true(zfs_btree_find(&bt, &absent, NULL) == NULL);
202*22649d4dSMartin Matuska
203*22649d4dSMartin Matuska zfs_btree_clear(&bt);
204*22649d4dSMartin Matuska zfs_btree_destroy(&bt);
205*22649d4dSMartin Matuska return (MUNIT_OK);
206*22649d4dSMartin Matuska }
207*22649d4dSMartin Matuska
208*22649d4dSMartin Matuska /*
209*22649d4dSMartin Matuska * Fill a B-Tree and an AVL tree with the same random values, then drain
210*22649d4dSMartin Matuska * both from alternating ends, checking they stay identical at every step.
211*22649d4dSMartin Matuska */
212*22649d4dSMartin Matuska static MunitResult
test_btree_drain(const MunitParameter params[],void * data)213*22649d4dSMartin Matuska test_btree_drain(const MunitParameter params[], void *data)
214*22649d4dSMartin Matuska {
215*22649d4dSMartin Matuska (void) params, (void) data;
216*22649d4dSMartin Matuska
217*22649d4dSMartin Matuska zfs_btree_t bt;
218*22649d4dSMartin Matuska avl_tree_t avl;
219*22649d4dSMartin Matuska zfs_btree_index_t bt_idx = {0};
220*22649d4dSMartin Matuska avl_index_t avl_idx = {0};
221*22649d4dSMartin Matuska int_node_t *node;
222*22649d4dSMartin Matuska
223*22649d4dSMartin Matuska btree_create_u64(&bt);
224*22649d4dSMartin Matuska avl_create(&avl, avl_u64_compare, sizeof (int_node_t),
225*22649d4dSMartin Matuska offsetof(int_node_t, node));
226*22649d4dSMartin Matuska
227*22649d4dSMartin Matuska /* Fill both trees with the same data. */
228*22649d4dSMartin Matuska for (int i = 0; i < DRAIN_COUNT; i++) {
229*22649d4dSMartin Matuska uint64_t randval = unit_rand_uint64();
230*22649d4dSMartin Matuska if (zfs_btree_find(&bt, &randval, &bt_idx) != NULL)
231*22649d4dSMartin Matuska continue;
232*22649d4dSMartin Matuska zfs_btree_add_idx(&bt, &randval, &bt_idx);
233*22649d4dSMartin Matuska
234*22649d4dSMartin Matuska node = malloc(sizeof (int_node_t));
235*22649d4dSMartin Matuska unit_true(node != NULL);
236*22649d4dSMartin Matuska node->data = randval;
237*22649d4dSMartin Matuska
238*22649d4dSMartin Matuska /* New to the btree, so the avl must not have it either. */
239*22649d4dSMartin Matuska unit_true(avl_find(&avl, node, &avl_idx) == NULL);
240*22649d4dSMartin Matuska avl_insert(&avl, node, avl_idx);
241*22649d4dSMartin Matuska }
242*22649d4dSMartin Matuska
243*22649d4dSMartin Matuska /* Remove from alternating ends, comparing the trees as we go. */
244*22649d4dSMartin Matuska while (avl_numnodes(&avl) != 0) {
245*22649d4dSMartin Matuska uint64_t *bt_data;
246*22649d4dSMartin Matuska
247*22649d4dSMartin Matuska unit_eq(zfs_btree_numnodes(&bt), avl_numnodes(&avl));
248*22649d4dSMartin Matuska if (avl_numnodes(&avl) % 2 == 0) {
249*22649d4dSMartin Matuska node = avl_first(&avl);
250*22649d4dSMartin Matuska bt_data = zfs_btree_first(&bt, &bt_idx);
251*22649d4dSMartin Matuska } else {
252*22649d4dSMartin Matuska node = avl_last(&avl);
253*22649d4dSMartin Matuska bt_data = zfs_btree_last(&bt, &bt_idx);
254*22649d4dSMartin Matuska }
255*22649d4dSMartin Matuska unit_eq(*bt_data, node->data);
256*22649d4dSMartin Matuska zfs_btree_remove_idx(&bt, &bt_idx);
257*22649d4dSMartin Matuska avl_remove(&avl, node);
258*22649d4dSMartin Matuska free(node);
259*22649d4dSMartin Matuska
260*22649d4dSMartin Matuska if (avl_numnodes(&avl) == 0)
261*22649d4dSMartin Matuska break;
262*22649d4dSMartin Matuska
263*22649d4dSMartin Matuska /* Both ends still agree after the removal. */
264*22649d4dSMartin Matuska uint64_t *bt_lo = zfs_btree_first(&bt, NULL);
265*22649d4dSMartin Matuska uint64_t *bt_hi = zfs_btree_last(&bt, NULL);
266*22649d4dSMartin Matuska int_node_t *avl_lo = avl_first(&avl);
267*22649d4dSMartin Matuska int_node_t *avl_hi = avl_last(&avl);
268*22649d4dSMartin Matuska unit_eq(*bt_lo, avl_lo->data);
269*22649d4dSMartin Matuska unit_eq(*bt_hi, avl_hi->data);
270*22649d4dSMartin Matuska }
271*22649d4dSMartin Matuska unit_zero(zfs_btree_numnodes(&bt));
272*22649d4dSMartin Matuska
273*22649d4dSMartin Matuska avl_destroy(&avl);
274*22649d4dSMartin Matuska zfs_btree_clear(&bt);
275*22649d4dSMartin Matuska zfs_btree_destroy(&bt);
276*22649d4dSMartin Matuska return (MUNIT_OK);
277*22649d4dSMartin Matuska }
278*22649d4dSMartin Matuska
279*22649d4dSMartin Matuska /* ========== */
280*22649d4dSMartin Matuska
281*22649d4dSMartin Matuska static const MunitTest btree_tests[] = {
282*22649d4dSMartin Matuska UNIT_TEST("empty", test_btree_empty),
283*22649d4dSMartin Matuska UNIT_TEST("add_find", test_btree_add_find),
284*22649d4dSMartin Matuska UNIT_TEST("remove", test_btree_remove),
285*22649d4dSMartin Matuska UNIT_TEST("walk", test_btree_walk),
286*22649d4dSMartin Matuska UNIT_TEST("find_without_index", test_btree_find_without_index),
287*22649d4dSMartin Matuska UNIT_TEST("drain", test_btree_drain),
288*22649d4dSMartin Matuska { 0 },
289*22649d4dSMartin Matuska };
290*22649d4dSMartin Matuska
291*22649d4dSMartin Matuska static const MunitSuite btree_test_suite = {
292*22649d4dSMartin Matuska "btree.",
293*22649d4dSMartin Matuska btree_tests,
294*22649d4dSMartin Matuska NULL,
295*22649d4dSMartin Matuska 1,
296*22649d4dSMartin Matuska MUNIT_SUITE_OPTION_NONE,
297*22649d4dSMartin Matuska };
298*22649d4dSMartin Matuska
299*22649d4dSMartin Matuska int
main(int argc,char ** argv)300*22649d4dSMartin Matuska main(int argc, char **argv)
301*22649d4dSMartin Matuska {
302*22649d4dSMartin Matuska zfs_btree_init();
303*22649d4dSMartin Matuska int ret = munit_suite_main(&btree_test_suite, NULL, argc, argv);
304*22649d4dSMartin Matuska zfs_btree_fini();
305*22649d4dSMartin Matuska return (ret);
306*22649d4dSMartin Matuska }
307