aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java')
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java634
1 files changed, 634 insertions, 0 deletions
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java
new file mode 100644
index 00000000..cbb48603
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java
@@ -0,0 +1,634 @@
1/*
2 * Copyright (c) 2021 Rodion Efremov
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0
6 */
7package tools.refinery.store.query.utils;
8
9import org.junit.jupiter.api.BeforeEach;
10import org.junit.jupiter.api.Test;
11import org.junit.jupiter.params.ParameterizedTest;
12import org.junit.jupiter.params.provider.Arguments;
13import org.junit.jupiter.params.provider.MethodSource;
14
15import java.util.*;
16import java.util.stream.Stream;
17
18import static org.junit.jupiter.api.Assertions.*;
19
20/**
21 * Tests for an order statistic tree which is based on AVL-trees.
22 * <p>
23 * This class was copied into <i>Refinery</i> from
24 * <a href="https://github.com/coderodde/OrderStatisticTree/tree/546c343b9f5d868e394a079ff32691c9dbfd83e3">https://github.com/coderodde/OrderStatisticTree</a>
25 * and is available under the
26 * <a href="https://github.com/coderodde/OrderStatisticTree/blob/master/LICENSE">MIT License</a>.
27 * We also migrated the code to Junit 5, cleaned up some linter warnings, and made the tests deterministic by fixing
28 * the random seeds.
29 *
30 * @author Rodion "rodde" Efremov
31 * @version based on 1.6 (Feb 11, 2016)
32 */
33class OrderStatisticTreeTest {
34 private final OrderStatisticTree<Integer> tree = new OrderStatisticTree<>();
35
36 private final TreeSet<Integer> set = new TreeSet<>();
37
38 @BeforeEach
39 void before() {
40 tree.clear();
41 set.clear();
42 }
43
44 @Test
45 void testAdd() {
46 assertEquals(set.isEmpty(), tree.isEmpty());
47
48 for (int i = 10; i < 30; i += 2) {
49 assertTrue(tree.isHealthy());
50 assertEquals(set.contains(i), tree.contains(i));
51 assertEquals(set.add(i), tree.add(i));
52 assertEquals(set.add(i), tree.add(i));
53 assertEquals(set.contains(i), tree.contains(i));
54 assertTrue(tree.isHealthy());
55 }
56
57 assertEquals(set.isEmpty(), tree.isEmpty());
58 }
59
60 @Test
61 void testAddAll() {
62 for (int i = 0; i < 10; ++i) {
63 assertEquals(set.add(i), tree.add(i));
64 }
65
66 Collection<Integer> coll = Arrays.asList(10, 9, 7, 11, 12);
67
68 assertEquals(set.addAll(coll), tree.addAll(coll));
69 assertEquals(set.size(), tree.size());
70
71 for (int i = -10; i < 20; ++i) {
72 assertEquals(set.contains(i), tree.contains(i));
73 }
74 }
75
76 @Test
77 void testClear() {
78 for (int i = 0; i < 2000; ++i) {
79 set.add(i);
80 tree.add(i);
81 }
82
83 assertEquals(set.size(), tree.size());
84 set.clear();
85 tree.clear();
86 // We expect {@code tree.size()} to always be 0, but we also test for it.
87 //noinspection ConstantValue
88 assertEquals(0, tree.size());
89 }
90
91 @Test
92 void testContains() {
93 for (int i = 100; i < 200; i += 3) {
94 assertTrue(tree.isHealthy());
95 assertEquals(set.add(i), tree.add(i));
96 assertTrue(tree.isHealthy());
97 }
98
99 assertEquals(set.size(), tree.size());
100
101 for (int i = 0; i < 300; ++i) {
102 assertEquals(set.contains(i), tree.contains(i));
103 }
104 }
105
106 @Test
107 void testContainsAll() {
108 for (int i = 0; i < 50; ++i) {
109 set.add(i);
110 tree.add(i);
111 }
112
113 Collection<Integer> coll = new HashSet<>();
114
115 for (int i = 10; i < 20; ++i) {
116 coll.add(i);
117 }
118
119 assertEquals(set.containsAll(coll), tree.containsAll(coll));
120 coll.add(100);
121 assertEquals(set.containsAll(coll), tree.containsAll(coll));
122 }
123
124 @Test
125 void testRemove() {
126 for (int i = 0; i < 200; ++i) {
127 assertEquals(set.add(i), tree.add(i));
128 }
129
130 for (int i = 50; i < 150; i += 2) {
131 assertEquals(set.remove(i), tree.remove(i));
132 assertTrue(tree.isHealthy());
133 }
134
135 for (int i = -100; i < 300; ++i) {
136 assertEquals(set.contains(i), tree.contains(i));
137 }
138 }
139
140 @Test
141 void testRemoveLast() {
142 tree.add(1);
143 tree.remove(1);
144 assertEquals(0, tree.size());
145 }
146
147 @Test
148 void testRemoveAll() {
149 for (int i = 0; i < 40; ++i) {
150 set.add(i);
151 tree.add(i);
152 }
153
154 Collection<Integer> coll = new HashSet<>();
155
156 for (int i = 10; i < 20; ++i) {
157 coll.add(i);
158 }
159
160 assertEquals(set.removeAll(coll), tree.removeAll(coll));
161
162 for (int i = -10; i < 50; ++i) {
163 assertEquals(set.contains(i), tree.contains(i));
164 }
165
166 assertEquals(set.removeAll(coll), tree.removeAll(coll));
167
168 for (int i = -10; i < 50; ++i) {
169 assertEquals(set.contains(i), tree.contains(i));
170 }
171 }
172
173 @Test
174 void testSize() {
175 for (int i = 0; i < 200; ++i) {
176 assertEquals(set.size(), tree.size());
177 assertEquals(set.add(i), tree.add(i));
178 assertEquals(set.size(), tree.size());
179 }
180 }
181
182 @Test
183 void testIndexOf() {
184 for (int i = 0; i < 100; ++i) {
185 assertTrue(tree.add(i * 2));
186 }
187
188 for (int i = 0; i < 100; ++i) {
189 assertEquals(i, tree.indexOf(2 * i));
190 }
191
192 for (int i = 100; i < 150; ++i) {
193 assertEquals(-1, tree.indexOf(2 * i));
194 }
195 }
196
197 @Test
198 void testEmpty() {
199 assertEquals(set.isEmpty(), tree.isEmpty());
200 set.add(0);
201 tree.add(0);
202 assertEquals(set.isEmpty(), tree.isEmpty());
203 }
204
205 @Test
206 void testEmptyTreeGetThrowsOnNegativeIndex() {
207 assertThrows(IndexOutOfBoundsException.class, () -> tree.get(-1));
208 }
209
210 @Test
211 void testEmptyTreeSelectThrowsOnTooLargeIndex() {
212 assertThrows(IndexOutOfBoundsException.class, () -> tree.get(0));
213 }
214
215 @Test
216 void testSelectThrowsOnNegativeIndex() {
217 for (int i = 0; i < 5; ++i) {
218 tree.add(i);
219 }
220
221 assertThrows(IndexOutOfBoundsException.class, () -> tree.get(-1));
222 }
223
224 @Test
225 void testSelectThrowsOnTooLargeIndex() {
226 for (int i = 0; i < 5; ++i) {
227 tree.add(i);
228 }
229
230 assertThrows(IndexOutOfBoundsException.class, () -> tree.get(5));
231 }
232
233 @Test
234 void testGet() {
235 for (int i = 0; i < 100; i += 3) {
236 tree.add(i);
237 }
238
239 for (int i = 0; i < tree.size(); ++i) {
240 assertEquals(Integer.valueOf(3 * i), tree.get(i));
241 }
242 }
243
244 @Test
245 void findBug() {
246 tree.add(0);
247 assertTrue(tree.isHealthy());
248
249 tree.add(-1);
250 tree.remove(-1);
251 assertTrue(tree.isHealthy());
252
253 tree.add(1);
254 tree.remove(1);
255 assertTrue(tree.isHealthy());
256
257 tree.add(-1);
258 tree.add(1);
259 tree.remove(0);
260 assertTrue(tree.isHealthy());
261
262 tree.clear();
263 tree.add(0);
264 tree.add(-1);
265 tree.add(10);
266 tree.add(5);
267 tree.add(15);
268 tree.add(11);
269 tree.add(30);
270 tree.add(7);
271
272 tree.remove(-1);
273
274 assertTrue(tree.isHealthy());
275 }
276
277 @ParameterizedTest(name = "seed = {0}")
278 @MethodSource("seedSource")
279 void tryReproduceTheCounterBug(long seed) {
280 Random random = new Random(seed);
281 List<Integer> list = new ArrayList<>();
282
283 for (int i = 0; i < 10; ++i) {
284 int number = random.nextInt(1000);
285 list.add(number);
286 tree.add(number);
287 assertTrue(tree.isHealthy());
288 }
289
290 for (Integer i : list) {
291 tree.remove(i);
292 boolean healthy = tree.isHealthy();
293 assertTrue(healthy);
294 }
295 }
296
297 @Test
298 void testEmptyIterator() {
299 var iterator = tree.iterator();
300 assertThrows(NoSuchElementException.class, iterator::next);
301 }
302
303 @Test
304 void testIteratorThrowsOnDoubleRemove() {
305 for (int i = 10; i < 20; ++i) {
306 set.add(i);
307 tree.add(i);
308 }
309
310 Iterator<Integer> iterator1 = set.iterator();
311 Iterator<Integer> iterator2 = tree.iterator();
312
313 for (int i = 0; i < 3; ++i) {
314 assertEquals(iterator1.next(), iterator2.next());
315 }
316
317 iterator1.remove();
318 iterator2.remove();
319
320 assertThrows(IllegalStateException.class, iterator1::remove);
321 assertThrows(IllegalStateException.class, iterator2::remove);
322 }
323
324 @Test
325 void testIterator() {
326 for (int i = 0; i < 5; ++i) {
327 tree.add(i);
328 set.add(i);
329 }
330
331 Iterator<Integer> iterator1 = set.iterator();
332 Iterator<Integer> iterator2 = tree.iterator();
333
334 for (int i = 0; i < 5; ++i) {
335 assertEquals(iterator1.hasNext(), iterator2.hasNext());
336 assertEquals(iterator1.next(), iterator2.next());
337 }
338
339 assertEquals(iterator1.hasNext(), iterator2.hasNext());
340
341 assertThrows(NoSuchElementException.class, iterator1::next);
342 assertThrows(NoSuchElementException.class, iterator2::next);
343 }
344
345 @Test
346 void testRemoveBeforeNextThrowsEmpty() {
347 var setIterator = set.iterator();
348 assertThrows(IllegalStateException.class, setIterator::remove);
349
350 var treeIterator = tree.iterator();
351 assertThrows(IllegalStateException.class, treeIterator::remove);
352 }
353
354 @Test
355 void testRemoveThrowsWithoutNext() {
356 for (int i = 0; i < 10; ++i) {
357 tree.add(i);
358 set.add(i);
359 }
360
361 Iterator<Integer> iterator1 = set.iterator();
362 Iterator<Integer> iterator2 = tree.iterator();
363
364 for (int i = 0; i < 4; ++i) {
365 assertEquals(iterator1.hasNext(), iterator2.hasNext());
366 assertEquals(iterator1.next(), iterator2.next());
367 }
368
369 iterator1.remove();
370 iterator2.remove();
371
372 assertThrows(IllegalStateException.class, iterator1::remove);
373 assertThrows(IllegalStateException.class, iterator2::remove);
374 }
375
376 @Test
377 void testRetainAll() {
378 for (int i = 0; i < 100; ++i) {
379 set.add(i);
380 tree.add(i);
381 }
382
383 Collection<Integer> coll = Arrays.asList(26, 29, 25);
384
385 assertEquals(set.retainAll(coll), tree.retainAll(coll));
386 assertEquals(set.size(), tree.size());
387
388 assertTrue(set.containsAll(tree));
389 assertTrue(tree.containsAll(set));
390 }
391
392 @Test
393 void testIteratorRemove() {
394 for (int i = 10; i < 16; ++i) {
395 assertEquals(set.add(i), tree.add(i));
396 }
397
398 Iterator<Integer> iterator1 = set.iterator();
399 Iterator<Integer> iterator2 = tree.iterator();
400
401 assertEquals(iterator1.hasNext(), iterator2.hasNext());
402 assertEquals(iterator1.next(), iterator2.next());
403
404 assertEquals(iterator1.hasNext(), iterator2.hasNext());
405 assertEquals(iterator1.next(), iterator2.next());
406
407 iterator1.remove(); // remove 11
408 iterator2.remove();
409
410 assertEquals(iterator1.hasNext(), iterator2.hasNext());
411 assertEquals(iterator1.next(), iterator2.next());
412
413 assertEquals(iterator1.hasNext(), iterator2.hasNext());
414 assertEquals(iterator1.next(), iterator2.next());
415
416 iterator1.remove(); // remove 13
417 iterator2.remove();
418
419 assertEquals(set.size(), tree.size());
420
421 for (int i = 10; i < 16; ++i) {
422 assertEquals(set.contains(i), tree.contains(i));
423 }
424 }
425
426 @ParameterizedTest(name = "seed = {0}")
427 @MethodSource("seedSource")
428 void testIteratorBruteForce(long seed) {
429 for (int i = 0; i < 1000; ++i) {
430 assertEquals(set.add(i), tree.add(i));
431 }
432
433 Iterator<Integer> iterator1 = set.iterator();
434 Iterator<Integer> iterator2 = tree.iterator();
435
436 Random random = new Random(seed);
437
438 while (true) {
439 if (!iterator1.hasNext()) {
440 assertFalse(iterator2.hasNext());
441 break;
442 }
443
444 boolean toRemove = random.nextBoolean();
445
446 if (toRemove) {
447 boolean thrown = false;
448
449 try {
450 iterator1.remove();
451 } catch (IllegalStateException ex) {
452 thrown = true;
453 }
454
455 if (thrown) {
456 assertThrows(IllegalStateException.class, iterator2::remove);
457 } else {
458 iterator2.remove();
459 }
460 } else {
461 assertEquals(iterator1.hasNext(), iterator2.hasNext());
462
463 if (iterator1.hasNext()) {
464 assertEquals(iterator1.next(), iterator2.next());
465 } else {
466 break;
467 }
468 }
469 }
470
471 assertEquals(set.size(), tree.size());
472 assertTrue(tree.isHealthy());
473 assertTrue(set.containsAll(tree));
474 assertTrue(tree.containsAll(set));
475 }
476
477 @Test
478 void testIteratorConcurrentModification() {
479 for (int i = 0; i < 100; ++i) {
480 set.add(i);
481 tree.add(i);
482 }
483
484 Iterator<Integer> iterator1 = set.iterator();
485 Iterator<Integer> iterator2 = tree.iterator();
486
487 set.remove(10);
488 tree.remove(10);
489
490 assertEquals(iterator1.hasNext(), iterator2.hasNext());
491
492 boolean thrown = false;
493
494 try {
495 iterator1.next();
496 } catch (ConcurrentModificationException ex) {
497 thrown = true;
498 }
499
500 if (thrown) {
501 assertThrows(ConcurrentModificationException.class, iterator2::next);
502 } else {
503 iterator2.next();
504 }
505 }
506
507 @Test
508 void testIteratorConcurrentRemove() {
509 for (int i = 10; i < 20; ++i) {
510 set.add(i);
511 tree.add(i);
512 }
513
514 Iterator<Integer> iterator1 = set.iterator();
515 Iterator<Integer> iterator2 = tree.iterator();
516
517 for (int i = 0; i < 4; ++i) {
518 iterator1.next();
519 iterator2.next();
520 }
521
522 // None of them contains 2, should not change the modification count.
523 set.remove(2);
524 tree.remove(2);
525
526 iterator1.remove();
527 iterator2.remove();
528
529 iterator1.next();
530 iterator2.next();
531
532 set.remove(12);
533 tree.remove(12);
534
535 // Both of them should throw.
536 assertThrows(ConcurrentModificationException.class, iterator1::remove);
537 assertThrows(ConcurrentModificationException.class, iterator2::remove);
538 }
539
540 @Test
541 void testConcurrentOrIllegalStateOnRemove() {
542 for (int i = 0; i < 10; ++i) {
543 set.add(i);
544 tree.add(i);
545 }
546
547 Iterator<Integer> iterator1 = set.iterator();
548 Iterator<Integer> iterator2 = tree.iterator();
549
550 set.add(100);
551 tree.add(100);
552
553 assertThrows(IllegalStateException.class, iterator1::remove);
554 assertThrows(IllegalStateException.class, iterator2::remove);
555 }
556
557 @Test
558 void testConcurrentIterators() {
559 for (int i = 0; i < 10; ++i) {
560 set.add(i);
561 tree.add(i);
562 }
563
564 Iterator<Integer> iterator1a = set.iterator();
565 Iterator<Integer> iterator1b = set.iterator();
566 Iterator<Integer> iterator2a = tree.iterator();
567 Iterator<Integer> iterator2b = tree.iterator();
568
569 for (int i = 0; i < 3; ++i) {
570 iterator1a.next();
571 iterator2a.next();
572 }
573
574 iterator1a.remove();
575 iterator2a.remove();
576
577 assertEquals(iterator1b.hasNext(), iterator2b.hasNext());
578
579 assertThrows(ConcurrentModificationException.class, iterator1b::next);
580 assertThrows(ConcurrentModificationException.class, iterator2b::next);
581 }
582
583 @ParameterizedTest(name = "seed = {0}")
584 @MethodSource("seedSource")
585 void testToArray(long seed) {
586 Random r = new Random(seed);
587
588 for (int i = 0; i < 50; ++i) {
589 int num = r.nextInt();
590 set.add(num);
591 tree.add(num);
592 }
593
594 assertArrayEquals(set.toArray(), tree.toArray());
595 }
596
597 @Test
598 void testToArrayGeneric() {
599 for (int i = 0; i < 100; ++i) {
600 set.add(i);
601 tree.add(i);
602 }
603
604 Integer[] array1before = new Integer[99];
605 Integer[] array2before = new Integer[99];
606
607 Integer[] array1after = set.toArray(array1before);
608 Integer[] array2after = tree.toArray(array2before);
609
610 assertNotSame(array1before, array1after);
611 assertNotSame(array2before, array2after);
612 assertArrayEquals(array1after, array2after);
613
614 set.remove(1);
615 tree.remove(1);
616
617 array1after = set.toArray(array1before);
618 array2after = tree.toArray(array2before);
619
620 assertSame(array1before, array1after);
621 assertSame(array2before, array2after);
622 assertArrayEquals(array1after, array2after);
623 }
624
625 static Stream<Arguments> seedSource() {
626 return Stream.of(
627 Arguments.of(0L),
628 Arguments.of(1L),
629 Arguments.of(2L),
630 Arguments.of(3L),
631 Arguments.of(4L)
632 );
633 }
634}