disjoint_interval_tree 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/test/c/test_dit.c ADDED
@@ -0,0 +1,1072 @@
1
+ /*
2
+ ** Copyright 2025 INRIA
3
+ **
4
+ ** Contributors :
5
+ ** Romain PEREIRA, romain.pereira@inria.fr + rpereira@anl.gov
6
+ **
7
+ ** This software is governed by the CeCILL-C license under French law and
8
+ ** abiding by the rules of distribution of free software. You can use,
9
+ ** modify and/ or redistribute the software under the terms of the CeCILL-C
10
+ ** license as circulated by CEA, CNRS and INRIA at the following URL
11
+ ** "http://www.cecill.info".
12
+ **
13
+ ** The fact that you are presently reading this means that you have had
14
+ ** knowledge of the CeCILL-C license and that you accept its terms.
15
+ */
16
+
17
+ /* Unit tests for the disjoint interval tree.
18
+ *
19
+ * Every test both checks the observable behaviour of the tree and its internal
20
+ * coherency through `dit_check()`. The last tests cross-check the tree against
21
+ * a naive O(n) reference implementation on randomized workloads */
22
+
23
+ #include "dit.h"
24
+
25
+ #include <inttypes.h>
26
+ #include <stdio.h>
27
+ #include <stdlib.h>
28
+ #include <string.h>
29
+
30
+ ////////////////////
31
+ // TEST HARNESS //
32
+ ////////////////////
33
+
34
+ static int tests_run = 0;
35
+ static int tests_failed = 0;
36
+ static int checks_run = 0;
37
+ static const char * current_test = NULL;
38
+
39
+ #define FAIL(...) \
40
+ do { \
41
+ fprintf(stderr, " %s:%d: FAILED in `%s`: ", \
42
+ __FILE__, __LINE__, current_test); \
43
+ fprintf(stderr, __VA_ARGS__); \
44
+ fprintf(stderr, "\n"); \
45
+ ++tests_failed; \
46
+ return ; \
47
+ } while (0)
48
+
49
+ #define ASSERT(COND) \
50
+ do { \
51
+ ++checks_run; \
52
+ if (!(COND)) \
53
+ FAIL("`%s`", #COND); \
54
+ } while (0)
55
+
56
+ #define ASSERT_EQ(X, Y) \
57
+ do { \
58
+ ++checks_run; \
59
+ const uint64_t __x = (uint64_t) (X); \
60
+ const uint64_t __y = (uint64_t) (Y); \
61
+ if (__x != __y) \
62
+ FAIL("`%s` is %" PRIu64 ", expected %" PRIu64 " (`%s`)", \
63
+ #X, __x, __y, #Y); \
64
+ } while (0)
65
+
66
+ /* a tree is coherent */
67
+ #define ASSERT_COHERENT(T) \
68
+ do { \
69
+ ++checks_run; \
70
+ char __err[512]; \
71
+ if (dit_check((T), __err, sizeof(__err))) \
72
+ FAIL("incoherent tree: %s", __err); \
73
+ } while (0)
74
+
75
+ #define RUN(F) \
76
+ do { \
77
+ const int __before = tests_failed; \
78
+ current_test = #F; \
79
+ ++tests_run; \
80
+ printf(" %-40s", #F); \
81
+ fflush(stdout); \
82
+ F(); \
83
+ printf("%s\n", (tests_failed == __before) ? "ok" : "FAILED"); \
84
+ } while (0)
85
+
86
+ ////////////////////////////
87
+ // REFERENCE (NAIVE) MODEL //
88
+ ////////////////////////////
89
+
90
+ /* A sorted array of disjoint intervals, used to cross-check the tree */
91
+
92
+ typedef struct
93
+ {
94
+ dit_value_t a, b;
95
+ } interval_t;
96
+
97
+ #define MODEL_CAPACITY 4096
98
+
99
+ typedef struct
100
+ {
101
+ interval_t intervals[MODEL_CAPACITY];
102
+ size_t n;
103
+ } model_t;
104
+
105
+ static void
106
+ model_init(model_t * m)
107
+ {
108
+ m->n = 0;
109
+ }
110
+
111
+ static int
112
+ model_intersect(const model_t * m, dit_value_t a, dit_value_t b)
113
+ {
114
+ for (size_t i = 0 ; i < m->n ; ++i)
115
+ if (a < m->intervals[i].b && m->intervals[i].a < b)
116
+ return 1;
117
+ return 0;
118
+ }
119
+
120
+ /* insert keeping the array sorted, returns 0 if it would overlap */
121
+ static int
122
+ model_insert(model_t * m, dit_value_t a, dit_value_t b)
123
+ {
124
+ if (a >= b || model_intersect(m, a, b))
125
+ return 0;
126
+
127
+ if (m->n == MODEL_CAPACITY)
128
+ {
129
+ fprintf(stderr, "model capacity exceeded\n");
130
+ abort();
131
+ }
132
+
133
+ size_t i = m->n;
134
+ while (i > 0 && m->intervals[i - 1].a > a)
135
+ {
136
+ m->intervals[i] = m->intervals[i - 1];
137
+ --i;
138
+ }
139
+ m->intervals[i].a = a;
140
+ m->intervals[i].b = b;
141
+ ++m->n;
142
+
143
+ return 1;
144
+ }
145
+
146
+ static size_t
147
+ model_remove(model_t * m, dit_value_t a, dit_value_t b)
148
+ {
149
+ if (a >= b)
150
+ return 0;
151
+
152
+ size_t w = 0;
153
+ size_t removed = 0;
154
+
155
+ for (size_t i = 0 ; i < m->n ; ++i)
156
+ {
157
+ if (a < m->intervals[i].b && m->intervals[i].a < b)
158
+ ++removed;
159
+ else
160
+ m->intervals[w++] = m->intervals[i];
161
+ }
162
+ m->n = w;
163
+
164
+ return removed;
165
+ }
166
+
167
+ ////////////////////////
168
+ // COLLECTING CALLBACK //
169
+ ////////////////////////
170
+
171
+ typedef struct
172
+ {
173
+ interval_t intervals[MODEL_CAPACITY];
174
+ size_t n;
175
+
176
+ /* stop the traversal after that many intervals, 0 to never stop */
177
+ size_t stop_after;
178
+ } collect_t;
179
+
180
+ static void
181
+ collect_init(collect_t * c)
182
+ {
183
+ c->n = 0;
184
+ c->stop_after = 0;
185
+ }
186
+
187
+ static int
188
+ collect_cb(dit_value_t a, dit_value_t b, void * user)
189
+ {
190
+ collect_t * c = (collect_t *) user;
191
+
192
+ if (c->n == MODEL_CAPACITY)
193
+ {
194
+ fprintf(stderr, "collect capacity exceeded\n");
195
+ abort();
196
+ }
197
+
198
+ c->intervals[c->n].a = a;
199
+ c->intervals[c->n].b = b;
200
+ ++c->n;
201
+
202
+ if (c->stop_after && c->n >= c->stop_after)
203
+ return 42;
204
+
205
+ return 0;
206
+ }
207
+
208
+ ///////////
209
+ // TESTS //
210
+ ///////////
211
+
212
+ static void
213
+ test_empty(void)
214
+ {
215
+ dit_t tree;
216
+ dit_init(&tree);
217
+
218
+ ASSERT_COHERENT(&tree);
219
+ ASSERT(dit_empty(&tree));
220
+ ASSERT_EQ(dit_size(&tree), 0);
221
+ ASSERT_EQ(dit_height(&tree), 0);
222
+ ASSERT(dit_at(&tree, 0) == NULL);
223
+ ASSERT(dit_intersecting(&tree, 0, 100) == NULL);
224
+ ASSERT_EQ(dit_intersect_p(&tree, 0, 100), 0);
225
+ ASSERT_EQ(dit_remove(&tree, 0, 100), 0);
226
+
227
+ /* an empty tree has no hull, and the out params are left untouched */
228
+ dit_value_t ha = 42;
229
+ dit_value_t hb = 43;
230
+ ASSERT_EQ(dit_hull(&tree, &ha, &hb), 0);
231
+ ASSERT_EQ(ha, 42);
232
+ ASSERT_EQ(hb, 43);
233
+
234
+ collect_t c;
235
+ collect_init(&c);
236
+ ASSERT_EQ(dit_intersect(&tree, 0, 100, collect_cb, &c), 0);
237
+ ASSERT_EQ(c.n, 0);
238
+ ASSERT_EQ(dit_each(&tree, collect_cb, &c), 0);
239
+ ASSERT_EQ(c.n, 0);
240
+
241
+ dit_destroy(&tree);
242
+ }
243
+
244
+ static void
245
+ test_insert_single(void)
246
+ {
247
+ dit_t tree;
248
+ dit_init(&tree);
249
+
250
+ ASSERT_EQ(dit_insert(&tree, 10, 20), DIT_OK);
251
+ ASSERT_COHERENT(&tree);
252
+ ASSERT_EQ(dit_size(&tree), 1);
253
+ ASSERT_EQ(dit_empty(&tree), 0);
254
+ ASSERT_EQ(dit_height(&tree), 1);
255
+
256
+ /* the hull of a single node is the node interval itself */
257
+ ASSERT_EQ(tree.root->augment.hull.a, 10);
258
+ ASSERT_EQ(tree.root->augment.hull.b, 20);
259
+ ASSERT_EQ(tree.root->augment.height, 1);
260
+ ASSERT_EQ(tree.root->augment.size, 1);
261
+
262
+ /* half-open interval: 10 is inside, 20 is not */
263
+ ASSERT(dit_at(&tree, 9) == NULL);
264
+ ASSERT(dit_at(&tree, 10) != NULL);
265
+ ASSERT(dit_at(&tree, 19) != NULL);
266
+ ASSERT(dit_at(&tree, 20) == NULL);
267
+
268
+ dit_destroy(&tree);
269
+ }
270
+
271
+ static void
272
+ test_insert_empty_interval(void)
273
+ {
274
+ dit_t tree;
275
+ dit_init(&tree);
276
+
277
+ ASSERT_EQ(dit_insert(&tree, 10, 10), DIT_EMPTY);
278
+ ASSERT_EQ(dit_insert(&tree, 20, 10), DIT_EMPTY);
279
+ ASSERT_EQ(dit_size(&tree), 0);
280
+ ASSERT_COHERENT(&tree);
281
+
282
+ dit_destroy(&tree);
283
+ }
284
+
285
+ static void
286
+ test_insert_overlap_is_rejected(void)
287
+ {
288
+ dit_t tree;
289
+ dit_init(&tree);
290
+
291
+ ASSERT_EQ(dit_insert(&tree, 10, 20), DIT_OK);
292
+ ASSERT_EQ(dit_insert(&tree, 30, 40), DIT_OK);
293
+
294
+ /* every flavour of overlap */
295
+ ASSERT_EQ(dit_insert(&tree, 10, 20), DIT_OVERLAP); /* identical */
296
+ ASSERT_EQ(dit_insert(&tree, 12, 18), DIT_OVERLAP); /* strictly included */
297
+ ASSERT_EQ(dit_insert(&tree, 5, 25), DIT_OVERLAP); /* strictly includes */
298
+ ASSERT_EQ(dit_insert(&tree, 5, 11), DIT_OVERLAP); /* overlaps the left */
299
+ ASSERT_EQ(dit_insert(&tree, 19, 25), DIT_OVERLAP); /* overlaps the right*/
300
+ ASSERT_EQ(dit_insert(&tree, 0, 35), DIT_OVERLAP); /* spans both */
301
+
302
+ /* and the tree was left untouched */
303
+ ASSERT_EQ(dit_size(&tree), 2);
304
+ ASSERT_COHERENT(&tree);
305
+
306
+ /* adjacent intervals do not overlap */
307
+ ASSERT_EQ(dit_insert(&tree, 0, 10), DIT_OK);
308
+ ASSERT_EQ(dit_insert(&tree, 20, 30), DIT_OK);
309
+ ASSERT_EQ(dit_insert(&tree, 40, 50), DIT_OK);
310
+ ASSERT_EQ(dit_size(&tree), 5);
311
+ ASSERT_COHERENT(&tree);
312
+
313
+ dit_destroy(&tree);
314
+ }
315
+
316
+ static void
317
+ test_ordering(void)
318
+ {
319
+ dit_t tree;
320
+ dit_init(&tree);
321
+
322
+ /* inserted out of order */
323
+ const dit_value_t starts[] = { 50, 10, 90, 30, 70, 0, 20 };
324
+ for (size_t i = 0 ; i < sizeof(starts) / sizeof(*starts) ; ++i)
325
+ ASSERT_EQ(dit_insert(&tree, starts[i], starts[i] + 5), DIT_OK);
326
+ ASSERT_COHERENT(&tree);
327
+
328
+ collect_t c;
329
+ collect_init(&c);
330
+ ASSERT_EQ(dit_each(&tree, collect_cb, &c), 0);
331
+
332
+ /* reported in increasing order */
333
+ ASSERT_EQ(c.n, 7);
334
+ for (size_t i = 1 ; i < c.n ; ++i)
335
+ ASSERT(c.intervals[i - 1].b <= c.intervals[i].a);
336
+ ASSERT_EQ(c.intervals[0].a, 0);
337
+ ASSERT_EQ(c.intervals[6].a, 90);
338
+
339
+ dit_destroy(&tree);
340
+ }
341
+
342
+ /* the hull augment must follow every mutation, wherever it happens in the
343
+ * tree, and whatever rotations it triggers */
344
+ static void
345
+ test_hull_augment(void)
346
+ {
347
+ dit_t tree;
348
+ dit_init(&tree);
349
+
350
+ dit_value_t a, b;
351
+
352
+ ASSERT_EQ(dit_insert(&tree, 100, 110), DIT_OK);
353
+ ASSERT_EQ(dit_hull(&tree, &a, &b), 1);
354
+ ASSERT_EQ(a, 100);
355
+ ASSERT_EQ(b, 110);
356
+
357
+ /* growing on the left moves the lower bound only */
358
+ ASSERT_EQ(dit_insert(&tree, 10, 20), DIT_OK);
359
+ ASSERT_EQ(dit_hull(&tree, &a, &b), 1);
360
+ ASSERT_EQ(a, 10);
361
+ ASSERT_EQ(b, 110);
362
+
363
+ /* growing on the right moves the upper bound only */
364
+ ASSERT_EQ(dit_insert(&tree, 200, 210), DIT_OK);
365
+ ASSERT_EQ(dit_hull(&tree, &a, &b), 1);
366
+ ASSERT_EQ(a, 10);
367
+ ASSERT_EQ(b, 210);
368
+
369
+ /* inserting in between changes nothing */
370
+ ASSERT_EQ(dit_insert(&tree, 50, 60), DIT_OK);
371
+ ASSERT_EQ(dit_hull(&tree, &a, &b), 1);
372
+ ASSERT_EQ(a, 10);
373
+ ASSERT_EQ(b, 210);
374
+
375
+ /* enough insertions to trigger rotations at the root */
376
+ for (dit_value_t i = 0 ; i < 64 ; ++i)
377
+ ASSERT_EQ(dit_insert(&tree, 1000 + 10 * i, 1000 + 10 * i + 5), DIT_OK);
378
+ ASSERT_COHERENT(&tree);
379
+ ASSERT_EQ(dit_hull(&tree, &a, &b), 1);
380
+ ASSERT_EQ(a, 10);
381
+ ASSERT_EQ(b, 1635);
382
+
383
+ /* the hull shrinks back when the extremities are removed */
384
+ ASSERT_EQ(dit_remove(&tree, 10, 20), 1);
385
+ ASSERT_EQ(dit_remove(&tree, 1630, 1635), 1);
386
+ ASSERT_COHERENT(&tree);
387
+ ASSERT_EQ(dit_hull(&tree, &a, &b), 1);
388
+ ASSERT_EQ(a, 50);
389
+ ASSERT_EQ(b, 1625);
390
+
391
+ /* the hull of a subtree englobes it, and only it */
392
+ ASSERT(tree.root->augment.hull.a == 50);
393
+ ASSERT(tree.root->augment.hull.b == 1625);
394
+ if (tree.root->left)
395
+ ASSERT(tree.root->left->augment.hull.b <= tree.root->a);
396
+ if (tree.root->right)
397
+ ASSERT(tree.root->right->augment.hull.a >= tree.root->b);
398
+
399
+ /* emptying the tree removes the hull */
400
+ dit_clear(&tree);
401
+ ASSERT_EQ(dit_hull(&tree, &a, &b), 0);
402
+
403
+ dit_destroy(&tree);
404
+ }
405
+
406
+ static void
407
+ test_intersect(void)
408
+ {
409
+ dit_t tree;
410
+ dit_init(&tree);
411
+
412
+ /* [0..10[ [20..30[ [40..50[ [60..70[ */
413
+ for (dit_value_t i = 0 ; i < 4 ; ++i)
414
+ ASSERT_EQ(dit_insert(&tree, 20 * i, 20 * i + 10), DIT_OK);
415
+ ASSERT_COHERENT(&tree);
416
+
417
+ collect_t c;
418
+
419
+ /* query strictly inside a hole */
420
+ collect_init(&c);
421
+ dit_intersect(&tree, 12, 18, collect_cb, &c);
422
+ ASSERT_EQ(c.n, 0);
423
+
424
+ /* query touching nothing, adjacency is not an intersection */
425
+ collect_init(&c);
426
+ dit_intersect(&tree, 10, 20, collect_cb, &c);
427
+ ASSERT_EQ(c.n, 0);
428
+
429
+ /* query overlapping a single interval by one unit */
430
+ collect_init(&c);
431
+ dit_intersect(&tree, 9, 20, collect_cb, &c);
432
+ ASSERT_EQ(c.n, 1);
433
+ ASSERT_EQ(c.intervals[0].a, 0);
434
+
435
+ /* query spanning several intervals */
436
+ collect_init(&c);
437
+ dit_intersect(&tree, 5, 45, collect_cb, &c);
438
+ ASSERT_EQ(c.n, 3);
439
+ ASSERT_EQ(c.intervals[0].a, 0);
440
+ ASSERT_EQ(c.intervals[1].a, 20);
441
+ ASSERT_EQ(c.intervals[2].a, 40);
442
+
443
+ /* query spanning everything */
444
+ collect_init(&c);
445
+ dit_intersect(&tree, 0, 1000, collect_cb, &c);
446
+ ASSERT_EQ(c.n, 4);
447
+
448
+ /* empty query */
449
+ collect_init(&c);
450
+ dit_intersect(&tree, 5, 5, collect_cb, &c);
451
+ ASSERT_EQ(c.n, 0);
452
+
453
+ /* out of range queries */
454
+ collect_init(&c);
455
+ dit_intersect(&tree, 1000, 2000, collect_cb, &c);
456
+ ASSERT_EQ(c.n, 0);
457
+
458
+ ASSERT_EQ(dit_intersect_p(&tree, 12, 18), 0);
459
+ ASSERT_EQ(dit_intersect_p(&tree, 9, 11), 1);
460
+
461
+ dit_destroy(&tree);
462
+ }
463
+
464
+ static void
465
+ test_intersect_early_stop(void)
466
+ {
467
+ dit_t tree;
468
+ dit_init(&tree);
469
+
470
+ for (dit_value_t i = 0 ; i < 100 ; ++i)
471
+ ASSERT_EQ(dit_insert(&tree, 10 * i, 10 * i + 5), DIT_OK);
472
+
473
+ collect_t c;
474
+ collect_init(&c);
475
+ c.stop_after = 3;
476
+
477
+ /* the callback return value is propagated */
478
+ ASSERT_EQ(dit_intersect(&tree, 0, 10000, collect_cb, &c), 42);
479
+ ASSERT_EQ(c.n, 3);
480
+ ASSERT_EQ(c.intervals[0].a, 0);
481
+ ASSERT_EQ(c.intervals[1].a, 10);
482
+ ASSERT_EQ(c.intervals[2].a, 20);
483
+
484
+ /* and the tree is untouched, and still traversable */
485
+ ASSERT_EQ(tree.traversing, 0);
486
+ ASSERT_COHERENT(&tree);
487
+
488
+ dit_destroy(&tree);
489
+ }
490
+
491
+ static void
492
+ test_remove(void)
493
+ {
494
+ dit_t tree;
495
+ dit_init(&tree);
496
+
497
+ /* [0..10[ [20..30[ [40..50[ [60..70[ */
498
+ for (dit_value_t i = 0 ; i < 4 ; ++i)
499
+ ASSERT_EQ(dit_insert(&tree, 20 * i, 20 * i + 10), DIT_OK);
500
+
501
+ /* removing a hole removes nothing */
502
+ ASSERT_EQ(dit_remove(&tree, 10, 20), 0);
503
+ ASSERT_EQ(dit_size(&tree), 4);
504
+ ASSERT_COHERENT(&tree);
505
+
506
+ /* intervals are removed as a whole, never split */
507
+ ASSERT_EQ(dit_remove(&tree, 25, 26), 1);
508
+ ASSERT_EQ(dit_size(&tree), 3);
509
+ ASSERT(dit_at(&tree, 20) == NULL);
510
+ ASSERT(dit_at(&tree, 29) == NULL);
511
+ ASSERT_COHERENT(&tree);
512
+
513
+ /* removing a range spanning several intervals */
514
+ ASSERT_EQ(dit_remove(&tree, 5, 45), 2);
515
+ ASSERT_EQ(dit_size(&tree), 1);
516
+ ASSERT(dit_at(&tree, 65) != NULL);
517
+ ASSERT_COHERENT(&tree);
518
+
519
+ /* removing everything */
520
+ ASSERT_EQ(dit_remove(&tree, 0, 1000), 1);
521
+ ASSERT_EQ(dit_size(&tree), 0);
522
+ ASSERT(dit_empty(&tree));
523
+ ASSERT_COHERENT(&tree);
524
+
525
+ /* removing from an empty tree */
526
+ ASSERT_EQ(dit_remove(&tree, 0, 1000), 0);
527
+ ASSERT_COHERENT(&tree);
528
+
529
+ dit_destroy(&tree);
530
+ }
531
+
532
+ static void
533
+ test_remove_reinsert(void)
534
+ {
535
+ dit_t tree;
536
+ dit_init(&tree);
537
+
538
+ for (dit_value_t i = 0 ; i < 256 ; ++i)
539
+ ASSERT_EQ(dit_insert(&tree, 10 * i, 10 * i + 5), DIT_OK);
540
+ ASSERT_COHERENT(&tree);
541
+
542
+ /* remove every other interval */
543
+ for (dit_value_t i = 0 ; i < 256 ; i += 2)
544
+ ASSERT_EQ(dit_remove(&tree, 10 * i, 10 * i + 5), 1);
545
+ ASSERT_EQ(dit_size(&tree), 128);
546
+ ASSERT_COHERENT(&tree);
547
+
548
+ /* the removed slots are now free again */
549
+ for (dit_value_t i = 0 ; i < 256 ; i += 2)
550
+ ASSERT_EQ(dit_insert(&tree, 10 * i, 10 * i + 5), DIT_OK);
551
+ ASSERT_EQ(dit_size(&tree), 256);
552
+ ASSERT_COHERENT(&tree);
553
+
554
+ /* the others are not */
555
+ for (dit_value_t i = 1 ; i < 256 ; i += 2)
556
+ ASSERT_EQ(dit_insert(&tree, 10 * i, 10 * i + 5), DIT_OVERLAP);
557
+ ASSERT_COHERENT(&tree);
558
+
559
+ dit_destroy(&tree);
560
+ }
561
+
562
+ static void
563
+ test_clear(void)
564
+ {
565
+ dit_t tree;
566
+ dit_init(&tree);
567
+
568
+ for (dit_value_t i = 0 ; i < 100 ; ++i)
569
+ ASSERT_EQ(dit_insert(&tree, 10 * i, 10 * i + 5), DIT_OK);
570
+
571
+ dit_clear(&tree);
572
+ ASSERT(dit_empty(&tree));
573
+ ASSERT_EQ(dit_size(&tree), 0);
574
+ ASSERT_COHERENT(&tree);
575
+
576
+ /* the tree is reusable */
577
+ ASSERT_EQ(dit_insert(&tree, 0, 1000), DIT_OK);
578
+ ASSERT_EQ(dit_size(&tree), 1);
579
+ ASSERT_COHERENT(&tree);
580
+
581
+ dit_destroy(&tree);
582
+ }
583
+
584
+ /* sequential insertions are the worst case for an unbalanced BST */
585
+ static void
586
+ test_balance_sorted_insertions(void)
587
+ {
588
+ dit_t tree;
589
+ dit_init(&tree);
590
+
591
+ const dit_value_t n = 4095;
592
+
593
+ for (dit_value_t i = 0 ; i < n ; ++i)
594
+ ASSERT_EQ(dit_insert(&tree, 2 * i, 2 * i + 1), DIT_OK);
595
+
596
+ ASSERT_EQ(dit_size(&tree), n);
597
+ ASSERT_COHERENT(&tree);
598
+
599
+ /* 4095 nodes fit in a perfectly balanced tree of height 12, an AVL tree
600
+ * is at most ~1.44*log2(n) deep */
601
+ ASSERT(dit_height(&tree) <= 18);
602
+
603
+ /* and reverse sorted insertions too */
604
+ dit_clear(&tree);
605
+ for (dit_value_t i = n ; i > 0 ; --i)
606
+ ASSERT_EQ(dit_insert(&tree, 2 * i, 2 * i + 1), DIT_OK);
607
+ ASSERT_EQ(dit_size(&tree), n);
608
+ ASSERT(dit_height(&tree) <= 18);
609
+ ASSERT_COHERENT(&tree);
610
+
611
+ /* sequential deletions from the left */
612
+ for (dit_value_t i = 1 ; i <= n ; ++i)
613
+ {
614
+ ASSERT_EQ(dit_remove(&tree, 2 * i, 2 * i + 1), 1);
615
+ ASSERT_EQ(dit_size(&tree), n - i);
616
+ }
617
+ ASSERT(dit_empty(&tree));
618
+ ASSERT_COHERENT(&tree);
619
+
620
+ dit_destroy(&tree);
621
+ }
622
+
623
+ /* the whole [0..UINT64_MAX[ range must be usable */
624
+ static void
625
+ test_extreme_values(void)
626
+ {
627
+ dit_t tree;
628
+ dit_init(&tree);
629
+
630
+ ASSERT_EQ(dit_insert(&tree, 0, 1), DIT_OK);
631
+ ASSERT_EQ(dit_insert(&tree, DIT_VALUE_MAX - 1, DIT_VALUE_MAX), DIT_OK);
632
+ ASSERT_EQ(dit_insert(&tree, 1, DIT_VALUE_MAX - 1), DIT_OK);
633
+ ASSERT_COHERENT(&tree);
634
+
635
+ ASSERT_EQ(tree.root->augment.hull.a, 0);
636
+ ASSERT_EQ(tree.root->augment.hull.b, DIT_VALUE_MAX);
637
+
638
+ /* DIT_VALUE_MAX can never be covered by a half-open interval */
639
+ ASSERT(dit_at(&tree, DIT_VALUE_MAX) == NULL);
640
+ ASSERT(dit_at(&tree, DIT_VALUE_MAX - 1) != NULL);
641
+ ASSERT(dit_at(&tree, 0) != NULL);
642
+
643
+ collect_t c;
644
+ collect_init(&c);
645
+ dit_intersect(&tree, 0, DIT_VALUE_MAX, collect_cb, &c);
646
+ ASSERT_EQ(c.n, 3);
647
+
648
+ ASSERT_EQ(dit_remove(&tree, 0, DIT_VALUE_MAX), 3);
649
+ ASSERT(dit_empty(&tree));
650
+ ASSERT_COHERENT(&tree);
651
+
652
+ dit_destroy(&tree);
653
+ }
654
+
655
+ /* `dit_check()` must actually detect a corrupted tree */
656
+ static void
657
+ test_check_detects_corruption(void)
658
+ {
659
+ char err[512];
660
+
661
+ dit_t tree;
662
+ dit_init(&tree);
663
+
664
+ for (dit_value_t i = 0 ; i < 32 ; ++i)
665
+ ASSERT_EQ(dit_insert(&tree, 10 * i, 10 * i + 5), DIT_OK);
666
+ ASSERT_EQ(dit_check(&tree, err, sizeof(err)), 0);
667
+
668
+ /* corrupt the cardinality */
669
+ tree.n += 1;
670
+ ASSERT(dit_check(&tree, err, sizeof(err)) != 0);
671
+ tree.n -= 1;
672
+ ASSERT_EQ(dit_check(&tree, err, sizeof(err)), 0);
673
+
674
+ /* corrupt the hull augment */
675
+ const dit_value_t saved_hull_b = tree.root->augment.hull.b;
676
+ tree.root->augment.hull.b -= 1;
677
+ ASSERT(dit_check(&tree, err, sizeof(err)) != 0);
678
+ tree.root->augment.hull.b = saved_hull_b;
679
+ ASSERT_EQ(dit_check(&tree, err, sizeof(err)), 0);
680
+
681
+ /* corrupt the height augment */
682
+ tree.root->augment.height += 1;
683
+ ASSERT(dit_check(&tree, err, sizeof(err)) != 0);
684
+ tree.root->augment.height -= 1;
685
+ ASSERT_EQ(dit_check(&tree, err, sizeof(err)), 0);
686
+
687
+ /* corrupt the size augment */
688
+ tree.root->augment.size += 1;
689
+ ASSERT(dit_check(&tree, err, sizeof(err)) != 0);
690
+ tree.root->augment.size -= 1;
691
+ ASSERT_EQ(dit_check(&tree, err, sizeof(err)), 0);
692
+
693
+ /* make the root interval empty */
694
+ const dit_value_t saved_a = tree.root->a;
695
+ tree.root->a = tree.root->b;
696
+ ASSERT(dit_check(&tree, err, sizeof(err)) != 0);
697
+ tree.root->a = saved_a;
698
+ ASSERT_EQ(dit_check(&tree, err, sizeof(err)), 0);
699
+
700
+ /* break the ordering, the root now overlaps its whole left subtree */
701
+ ASSERT(tree.root->left != NULL);
702
+ tree.root->a = 0;
703
+ ASSERT(dit_check(&tree, err, sizeof(err)) != 0);
704
+ tree.root->a = saved_a;
705
+ ASSERT_EQ(dit_check(&tree, err, sizeof(err)), 0);
706
+
707
+ dit_destroy(&tree);
708
+ }
709
+
710
+ static void
711
+ test_dump_dot(void)
712
+ {
713
+ dit_t tree;
714
+ dit_init(&tree);
715
+
716
+ FILE * f = fopen("/dev/null", "w");
717
+ if (f == NULL)
718
+ FAIL("could not open /dev/null");
719
+
720
+ /* an empty tree is dumpable */
721
+ dit_dump_dot(&tree, f);
722
+
723
+ for (dit_value_t i = 0 ; i < 16 ; ++i)
724
+ ASSERT_EQ(dit_insert(&tree, 10 * i, 10 * i + 5), DIT_OK);
725
+
726
+ dit_dump_dot(&tree, f);
727
+ dit_dump_dot(NULL, f);
728
+
729
+ fclose(f);
730
+
731
+ ASSERT_COHERENT(&tree);
732
+ dit_destroy(&tree);
733
+ }
734
+
735
+ ///////////////////////
736
+ // RANDOMIZED TESTS //
737
+ ///////////////////////
738
+
739
+ /* Seeds, sizes and intervals are decimal everywhere in this file. The few
740
+ * constants below are the published bit patterns of splitmix64 and
741
+ * xorshift64*, and are the only thing left in hexadecimal: they are chosen for
742
+ * their bits, not for their value, and writing them in decimal would only make
743
+ * them unrecognizable */
744
+ static uint64_t rng_state = 0x853c49e6748fea9bULL;
745
+
746
+ /* xorshift64* needs a non-zero, well spread state: run the seed through a
747
+ * splitmix64 avalanche so that consecutive seeds give unrelated streams.
748
+ * Simply forcing the state odd would make seeds 2k and 2k+1 equivalent, and
749
+ * halve the coverage of a `rake test:seeds` sweep */
750
+ static uint64_t
751
+ rng_seed(uint64_t seed)
752
+ {
753
+ seed += 0x9E3779B97F4A7C15ULL;
754
+ seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9ULL;
755
+ seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBULL;
756
+ seed ^= seed >> 31;
757
+ return seed ? seed : 1;
758
+ }
759
+
760
+ static uint64_t
761
+ rng_next(void)
762
+ {
763
+ /* xorshift64* */
764
+ rng_state ^= rng_state >> 12;
765
+ rng_state ^= rng_state << 25;
766
+ rng_state ^= rng_state >> 27;
767
+ return rng_state * 0x2545F4914F6CDD1DULL;
768
+ }
769
+
770
+ static uint64_t
771
+ rng_below(uint64_t n)
772
+ {
773
+ return rng_next() % n;
774
+ }
775
+
776
+ /* compare the tree content against the model, in-order */
777
+ static int
778
+ same_as_model(dit_t * tree, const model_t * m, collect_t * c)
779
+ {
780
+ collect_init(c);
781
+ dit_each(tree, collect_cb, c);
782
+
783
+ if (c->n != m->n)
784
+ return 0;
785
+
786
+ for (size_t i = 0 ; i < m->n ; ++i)
787
+ if (c->intervals[i].a != m->intervals[i].a ||
788
+ c->intervals[i].b != m->intervals[i].b)
789
+ return 0;
790
+
791
+ return 1;
792
+ }
793
+
794
+ static void
795
+ test_random_against_model(void)
796
+ {
797
+ const uint64_t universe = 512;
798
+ const int iterations = 20000;
799
+
800
+ dit_t tree;
801
+ dit_init(&tree);
802
+
803
+ model_t model;
804
+ model_init(&model);
805
+
806
+ collect_t c;
807
+
808
+ for (int it = 0 ; it < iterations ; ++it)
809
+ {
810
+ const dit_value_t a = rng_below(universe);
811
+ const dit_value_t len = 1 + rng_below(8);
812
+ const dit_value_t b = a + len;
813
+
814
+ switch (rng_below(3))
815
+ {
816
+ /* insert, only when the model says it does not overlap */
817
+ case 0:
818
+ {
819
+ const int insertable = !model_intersect(&model, a, b);
820
+ const dit_status_t status = dit_insert(&tree, a, b);
821
+
822
+ if (insertable)
823
+ {
824
+ if (status != DIT_OK)
825
+ FAIL("insert([%" PRIu64 "..%" PRIu64 "[) returned %d, "
826
+ "expected DIT_OK", a, b, (int) status);
827
+ model_insert(&model, a, b);
828
+ }
829
+ else
830
+ {
831
+ if (status != DIT_OVERLAP)
832
+ FAIL("insert([%" PRIu64 "..%" PRIu64 "[) returned %d, "
833
+ "expected DIT_OVERLAP", a, b, (int) status);
834
+ }
835
+ break ;
836
+ }
837
+
838
+ /* remove */
839
+ case 1:
840
+ {
841
+ const size_t expected = model_remove(&model, a, b);
842
+ const size_t got = dit_remove(&tree, a, b);
843
+ if (got != expected)
844
+ FAIL("remove([%" PRIu64 "..%" PRIu64 "[) removed %zu "
845
+ "intervals, expected %zu", a, b, got, expected);
846
+ break ;
847
+ }
848
+
849
+ /* intersect */
850
+ default:
851
+ {
852
+ collect_init(&c);
853
+ dit_intersect(&tree, a, b, collect_cb, &c);
854
+
855
+ size_t expected = 0;
856
+ for (size_t i = 0 ; i < model.n ; ++i)
857
+ if (a < model.intervals[i].b && model.intervals[i].a < b)
858
+ {
859
+ if (expected >= c.n ||
860
+ c.intervals[expected].a != model.intervals[i].a ||
861
+ c.intervals[expected].b != model.intervals[i].b)
862
+ FAIL("intersect([%" PRIu64 "..%" PRIu64 "[) "
863
+ "mismatch at %zu", a, b, expected);
864
+ ++expected;
865
+ }
866
+
867
+ if (c.n != expected)
868
+ FAIL("intersect([%" PRIu64 "..%" PRIu64 "[) reported %zu "
869
+ "intervals, expected %zu", a, b, c.n, expected);
870
+ break ;
871
+ }
872
+ }
873
+
874
+ /* the structure stays coherent at every single step */
875
+ {
876
+ char err[512];
877
+ if (dit_check(&tree, err, sizeof(err)))
878
+ FAIL("iteration %d: incoherent tree: %s", it, err);
879
+ }
880
+
881
+ if (dit_size(&tree) != model.n)
882
+ FAIL("iteration %d: tree holds %zu intervals, expected %zu",
883
+ it, dit_size(&tree), model.n);
884
+
885
+ if (!same_as_model(&tree, &model, &c))
886
+ FAIL("iteration %d: tree content differs from the model", it);
887
+ }
888
+
889
+ ++checks_run;
890
+ dit_destroy(&tree);
891
+ }
892
+
893
+ /* same thing, but on a large tree, checking coherency only from time to time
894
+ * so that the test remains fast */
895
+ static void
896
+ test_random_large(void)
897
+ {
898
+ /* the paranoid build checks the whole structure after every mutation,
899
+ * which is O(n): keep the tree small enough for the suite to stay fast */
900
+ const uint64_t universe = DIT_PARANOID ? 20000 : 1000000;
901
+ const int iterations = DIT_PARANOID ? 20000 : 200000;
902
+
903
+ dit_t tree;
904
+ dit_init(&tree);
905
+
906
+ size_t inserted = 0;
907
+ size_t removed = 0;
908
+
909
+ for (int it = 0 ; it < iterations ; ++it)
910
+ {
911
+ const dit_value_t a = rng_below(universe);
912
+ const dit_value_t b = a + 1 + rng_below(64);
913
+
914
+ if (rng_below(4) == 0)
915
+ removed += dit_remove(&tree, a, b);
916
+ else if (dit_insert(&tree, a, b) == DIT_OK)
917
+ ++inserted;
918
+
919
+ if ((it % 5000) == 0)
920
+ {
921
+ char err[512];
922
+ if (dit_check(&tree, err, sizeof(err)))
923
+ FAIL("iteration %d: incoherent tree: %s", it, err);
924
+ }
925
+ }
926
+
927
+ ASSERT_COHERENT(&tree);
928
+ ASSERT_EQ(dit_size(&tree), inserted - removed);
929
+ ASSERT(inserted > 1000);
930
+ ASSERT(removed > 1000);
931
+
932
+ /* removing everything must empty the tree */
933
+ dit_remove(&tree, 0, DIT_VALUE_MAX);
934
+ ASSERT(dit_empty(&tree));
935
+ ASSERT_COHERENT(&tree);
936
+
937
+ dit_destroy(&tree);
938
+ }
939
+
940
+ /* insert n intervals in a random order, then remove them in another random
941
+ * order, checking the structure at every step */
942
+ static void
943
+ test_random_insert_then_remove(void)
944
+ {
945
+ const size_t n = 1024;
946
+
947
+ dit_value_t * order = (dit_value_t *) malloc(n * sizeof(dit_value_t));
948
+ if (order == NULL)
949
+ FAIL("out of memory");
950
+
951
+ for (size_t i = 0 ; i < n ; ++i)
952
+ order[i] = (dit_value_t) i;
953
+
954
+ /* fisher-yates */
955
+ for (size_t i = n - 1 ; i > 0 ; --i)
956
+ {
957
+ const size_t j = (size_t) rng_below(i + 1);
958
+ const dit_value_t tmp = order[i];
959
+ order[i] = order[j];
960
+ order[j] = tmp;
961
+ }
962
+
963
+ dit_t tree;
964
+ dit_init(&tree);
965
+
966
+ for (size_t i = 0 ; i < n ; ++i)
967
+ {
968
+ if (dit_insert(&tree, 10 * order[i], 10 * order[i] + 7) != DIT_OK)
969
+ {
970
+ free(order);
971
+ FAIL("insertion %zu failed", i);
972
+ }
973
+ if (dit_size(&tree) != i + 1)
974
+ {
975
+ free(order);
976
+ FAIL("tree holds %zu intervals after %zu insertions",
977
+ dit_size(&tree), i + 1);
978
+ }
979
+ }
980
+
981
+ {
982
+ char err[512];
983
+ if (dit_check(&tree, err, sizeof(err)))
984
+ {
985
+ free(order);
986
+ FAIL("incoherent tree after insertions: %s", err);
987
+ }
988
+ }
989
+
990
+ /* reshuffle, then remove */
991
+ for (size_t i = n - 1 ; i > 0 ; --i)
992
+ {
993
+ const size_t j = (size_t) rng_below(i + 1);
994
+ const dit_value_t tmp = order[i];
995
+ order[i] = order[j];
996
+ order[j] = tmp;
997
+ }
998
+
999
+ for (size_t i = 0 ; i < n ; ++i)
1000
+ {
1001
+ if (dit_remove(&tree, 10 * order[i], 10 * order[i] + 7) != 1)
1002
+ {
1003
+ free(order);
1004
+ FAIL("removal %zu failed", i);
1005
+ }
1006
+ if (dit_size(&tree) != n - i - 1)
1007
+ {
1008
+ free(order);
1009
+ FAIL("tree holds %zu intervals after %zu removals",
1010
+ dit_size(&tree), i + 1);
1011
+ }
1012
+ if ((i % 64) == 0)
1013
+ {
1014
+ char err[512];
1015
+ if (dit_check(&tree, err, sizeof(err)))
1016
+ {
1017
+ free(order);
1018
+ FAIL("incoherent tree after %zu removals: %s", i, err);
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ free(order);
1024
+
1025
+ ASSERT(dit_empty(&tree));
1026
+ ASSERT_COHERENT(&tree);
1027
+
1028
+ dit_destroy(&tree);
1029
+ }
1030
+
1031
+ //////////
1032
+ // MAIN //
1033
+ //////////
1034
+
1035
+ int
1036
+ main(int argc, char ** argv)
1037
+ {
1038
+ uint64_t seed = 0;
1039
+ if (argc > 1)
1040
+ {
1041
+ /* base 10 explicitly: a `0`-prefixed seed is a decimal one, not octal */
1042
+ seed = strtoull(argv[1], NULL, 10);
1043
+ rng_state = rng_seed(seed);
1044
+ }
1045
+
1046
+ printf("running dit tests (seed=%" PRIu64 ", state=%" PRIu64 ", paranoid=%d)\n",
1047
+ seed, rng_state, DIT_PARANOID);
1048
+
1049
+ RUN(test_empty);
1050
+ RUN(test_insert_single);
1051
+ RUN(test_insert_empty_interval);
1052
+ RUN(test_insert_overlap_is_rejected);
1053
+ RUN(test_ordering);
1054
+ RUN(test_hull_augment);
1055
+ RUN(test_intersect);
1056
+ RUN(test_intersect_early_stop);
1057
+ RUN(test_remove);
1058
+ RUN(test_remove_reinsert);
1059
+ RUN(test_clear);
1060
+ RUN(test_balance_sorted_insertions);
1061
+ RUN(test_extreme_values);
1062
+ RUN(test_check_detects_corruption);
1063
+ RUN(test_dump_dot);
1064
+ RUN(test_random_against_model);
1065
+ RUN(test_random_large);
1066
+ RUN(test_random_insert_then_remove);
1067
+
1068
+ printf("\n%d tests, %d checks, %d failures\n",
1069
+ tests_run, checks_run, tests_failed);
1070
+
1071
+ return tests_failed ? 1 : 0;
1072
+ }