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.
- checksums.yaml +7 -0
- data/CITATION.cff +67 -0
- data/README.md +340 -0
- data/Rakefile +232 -0
- data/disjoint_interval_tree.gemspec +42 -0
- data/ext/disjoint_interval_tree/dit.c +850 -0
- data/ext/disjoint_interval_tree/dit.h +244 -0
- data/ext/disjoint_interval_tree/dit_ruby.c +648 -0
- data/ext/disjoint_interval_tree/extconf.rb +17 -0
- data/lib/disjoint_interval_tree/version.rb +5 -0
- data/lib/disjoint_interval_tree.rb +52 -0
- data/test/c/Makefile +58 -0
- data/test/c/test_dit.c +1072 -0
- data/test/test_disjoint_interval_tree.rb +649 -0
- metadata +65 -0
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
|
+
}
|