4 Copyright (c) 2018, Mungo Carstairs
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
10 * Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
13 * Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
17 * Neither the name of the copyright holder nor the names of its
18 contributors may be used to endorse or promote products derived from
19 this software without specific prior written permission.
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 package intervalstore.nonc;
34 import static org.testng.Assert.assertEquals;
35 import static org.testng.Assert.assertFalse;
36 import static org.testng.Assert.assertSame;
37 import static org.testng.Assert.assertTrue;
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Random;
43 import org.testng.annotations.Test;
45 import intervalstore.api.IntervalStoreI;
47 public class IntervalStoreTest
49 @Test(groups = "Functional")
50 public void testFindOverlaps_nonNested()
52 IntervalStore<SimpleFeature> store = new IntervalStore<>();
53 SimpleFeature sf1 = add(store, 10, 20);
54 // same range different description
55 SimpleFeature sf2 = new SimpleFeature(10, 20, "desc");
57 SimpleFeature sf3 = add(store, 15, 25);
58 SimpleFeature sf4 = add(store, 20, 35);
60 assertEquals(store.size(), 4);
61 List<SimpleFeature> overlaps = store.findOverlaps(1, 9);
62 assertTrue(overlaps.isEmpty());
64 overlaps = store.findOverlaps(8, 10);
65 assertEquals(overlaps.size(), 2);
66 assertTrue(overlaps.contains(sf1));
67 assertTrue(overlaps.contains(sf2));
69 overlaps = store.findOverlaps(12, 16);
70 assertEquals(overlaps.size(), 3);
71 assertTrue(overlaps.contains(sf1));
72 assertTrue(overlaps.contains(sf2));
73 assertTrue(overlaps.contains(sf3));
75 overlaps = store.findOverlaps(33, 33);
76 assertEquals(overlaps.size(), 1);
77 assertTrue(overlaps.contains(sf4));
80 * ensure edge cases are covered
82 overlaps = store.findOverlaps(35, 40);
83 assertEquals(overlaps.size(), 1);
84 assertTrue(overlaps.contains(sf4));
85 assertTrue(store.findOverlaps(36, 100).isEmpty());
86 assertTrue(store.findOverlaps(1, 9).isEmpty());
89 @Test(groups = "Functional")
90 public void testFindOverlaps_nested()
92 IntervalStore<SimpleFeature> store = new IntervalStore<>();
93 SimpleFeature sf1 = add(store, 10, 50);
94 SimpleFeature sf2 = add(store, 10, 40);
95 SimpleFeature sf3 = add(store, 20, 30);
96 // feature at same location but different description
97 SimpleFeature sf4 = new SimpleFeature(20, 30, "different desc");
99 SimpleFeature sf5 = add(store, 35, 36);
101 List<SimpleFeature> overlaps = store.findOverlaps(1, 9);
102 assertTrue(overlaps.isEmpty());
104 overlaps = store.findOverlaps(10, 15);
105 assertEquals(overlaps.size(), 2);
106 assertTrue(overlaps.contains(sf1));
107 assertTrue(overlaps.contains(sf2));
109 overlaps = store.findOverlaps(45, 60);
110 assertEquals(overlaps.size(), 1);
111 assertTrue(overlaps.contains(sf1));
113 overlaps = store.findOverlaps(32, 38);
114 assertEquals(overlaps.size(), 3);
115 assertTrue(overlaps.contains(sf1));
116 assertTrue(overlaps.contains(sf2));
117 assertTrue(overlaps.contains(sf5));
119 overlaps = store.findOverlaps(15, 25);
120 assertEquals(overlaps.size(), 4);
121 assertTrue(overlaps.contains(sf1));
122 assertTrue(overlaps.contains(sf2));
123 assertTrue(overlaps.contains(sf3));
124 assertTrue(overlaps.contains(sf4));
127 @Test(groups = "Functional")
128 public void testFindOverlaps_mixed()
130 IntervalStore<SimpleFeature> store = new IntervalStore<>();
131 SimpleFeature sf1 = add(store, 10, 50);
132 SimpleFeature sf2 = add(store, 1, 15);
133 SimpleFeature sf3 = add(store, 20, 30);
134 SimpleFeature sf4 = add(store, 40, 100);
135 SimpleFeature sf5 = add(store, 60, 100);
136 SimpleFeature sf6 = add(store, 70, 70);
138 List<SimpleFeature> overlaps = store.findOverlaps(200, 200);
139 assertTrue(overlaps.isEmpty());
141 overlaps = store.findOverlaps(1, 9);
142 assertEquals(overlaps.size(), 1);
143 assertTrue(overlaps.contains(sf2));
145 overlaps = store.findOverlaps(5, 18);
146 assertEquals(overlaps.size(), 2);
147 assertTrue(overlaps.contains(sf1));
148 assertTrue(overlaps.contains(sf2));
150 overlaps = store.findOverlaps(30, 40);
151 assertEquals(overlaps.size(), 3);
152 assertTrue(overlaps.contains(sf1));
153 assertTrue(overlaps.contains(sf3));
154 assertTrue(overlaps.contains(sf4));
156 overlaps = store.findOverlaps(80, 90);
157 assertEquals(overlaps.size(), 2);
158 assertTrue(overlaps.contains(sf4));
159 assertTrue(overlaps.contains(sf5));
161 overlaps = store.findOverlaps(68, 70);
162 assertEquals(overlaps.size(), 3);
163 assertTrue(overlaps.contains(sf4));
164 assertTrue(overlaps.contains(sf5));
165 assertTrue(overlaps.contains(sf6));
169 * Helper method to add a feature with type "desc"
176 SimpleFeature add(IntervalStore<SimpleFeature> store, int from,
179 SimpleFeature sf1 = new SimpleFeature(from, to, "desc");
184 @Test(groups = "Functional")
185 public void testRemove()
187 IntervalStore<SimpleFeature> store = new IntervalStore<>();
188 SimpleFeature sf1 = add(store, 10, 20);
189 assertTrue(store.contains(sf1));
193 store.remove("what is this?");
194 } catch (ClassCastException e)
198 assertFalse(store.remove(null));
203 assertTrue(store.remove(sf1));
204 assertTrue(store.isEmpty());
206 SimpleFeature sf2 = add(store, 0, 0);
207 SimpleFeature sf2a = add(store, 30, 40);
208 assertTrue(store.contains(sf2));
209 assertFalse(store.remove(sf1));
210 assertTrue(store.remove(sf2));
211 assertTrue(store.remove(sf2a));
212 assertTrue(store.isEmpty());
215 * nested feature deletion
217 SimpleFeature sf4 = add(store, 20, 30);
218 SimpleFeature sf5 = add(store, 22, 26); // to NCList
219 SimpleFeature sf6 = add(store, 23, 24); // child of sf5
220 SimpleFeature sf7 = add(store, 25, 25); // sibling of sf6
221 SimpleFeature sf8 = add(store, 24, 24); // child of sf6
222 SimpleFeature sf9 = add(store, 23, 23); // child of sf6
223 assertEquals(store.size(), 6);
225 // delete a node with children - they take its place
226 assertTrue(store.remove(sf6)); // sf8, sf9 should become children of sf5
227 assertEquals(store.size(), 5);
228 assertFalse(store.contains(sf6));
230 // delete a node with no children
231 assertTrue(store.remove(sf7));
232 assertEquals(store.size(), 4);
233 assertFalse(store.contains(sf7));
235 // delete root of NCList
236 assertTrue(store.remove(sf5));
237 assertEquals(store.size(), 3);
238 assertFalse(store.contains(sf5));
240 // continue the killing fields
241 assertTrue(store.remove(sf4));
242 assertEquals(store.size(), 2);
243 assertFalse(store.contains(sf4));
245 assertTrue(store.remove(sf9));
246 assertEquals(store.size(), 1);
247 assertFalse(store.contains(sf9));
249 assertTrue(store.remove(sf8));
250 assertTrue(store.isEmpty());
254 * A helper method to test whether a list contains a specific object (by
255 * object identity, not equality test as used by List.contains())
261 private static boolean containsObject(List<? extends Object> list,
264 for (Object i : list)
274 @Test(groups = "Functional")
275 public void testAdd()
277 IntervalStore<SimpleFeature> store = new IntervalStore<>();
279 assertFalse(store.add(null));
281 SimpleFeature sf1 = new SimpleFeature(10, 20, "Cath");
282 SimpleFeature sf2 = new SimpleFeature(10, 20, "Cath");
284 assertTrue(store.add(sf1));
285 assertEquals(store.size(), 1);
288 * contains should return true for the same or an identical feature
290 assertTrue(store.contains(sf1));
291 assertTrue(store.contains(sf2));
294 * duplicates are accepted
296 assertTrue(store.add(sf2));
297 assertEquals(store.size(), 2);
299 SimpleFeature sf3 = new SimpleFeature(0, 0, "Cath");
300 assertTrue(store.add(sf3));
301 assertEquals(store.size(), 3);
304 @Test(groups = "Functional")
305 public void testAdd_noDuplicates()
307 IntervalStore<SimpleFeature> store = new IntervalStore<>();
309 SimpleFeature sf1 = new SimpleFeature(10, 20, "Cath");
310 SimpleFeature sf2 = new SimpleFeature(10, 20, "Cath");
311 assertTrue(sf1.equals(sf2));
312 assertTrue(store.add(sf1));
313 assertEquals(store.size(), 1);
314 assertFalse(store.add(sf2, false));
315 assertEquals(store.size(), 1);
316 assertTrue(store.contains(sf1));
317 assertTrue(store.contains(sf2)); // because sf1.equals(sf2) !
320 @Test(groups = "Functional")
321 public void testIsEmpty()
323 IntervalStore<SimpleFeature> store = new IntervalStore<>();
324 assertTrue(store.isEmpty());
325 assertEquals(store.size(), 0);
330 SimpleFeature sf1 = new SimpleFeature(10, 20, "Cath");
332 assertFalse(store.isEmpty());
333 assertEquals(store.size(), 1);
335 assertTrue(store.isEmpty());
336 assertEquals(store.size(), 0);
338 sf1 = new SimpleFeature(0, 0, "Cath");
340 assertFalse(store.isEmpty());
341 assertEquals(store.size(), 1);
343 assertTrue(store.isEmpty());
344 assertEquals(store.size(), 0);
347 * sf2, sf3 added as nested features
349 sf1 = new SimpleFeature(19, 49, "Cath");
350 SimpleFeature sf2 = new SimpleFeature(20, 40, "Cath");
351 SimpleFeature sf3 = new SimpleFeature(25, 35, "Cath");
353 assertEquals(store.size(), 1);
355 assertEquals(store.size(), 2);
357 assertEquals(store.size(), 3);
358 assertTrue(store.remove(sf1));
359 assertEquals(store.size(), 2);
361 assertFalse(store.isEmpty());
362 assertTrue(store.remove(sf2));
363 assertEquals(store.size(), 1);
364 assertFalse(store.isEmpty());
365 assertTrue(store.remove(sf3));
366 assertEquals(store.size(), 0);
367 assertTrue(store.isEmpty()); // all gone
370 @Test(groups = "Functional")
371 public void testRemove_readd()
374 * add a feature and a nested feature
376 IntervalStore<SimpleFeature> store = new IntervalStore<>();
377 SimpleFeature sf1 = add(store, 10, 20);
378 // sf2 is nested in sf1 so will be stored in nestedFeatures
379 SimpleFeature sf2 = add(store, 12, 14);
380 assertEquals(store.size(), 2);
381 assertTrue(store.contains(sf1));
382 assertTrue(store.contains(sf2));
385 * delete the first feature
387 assertTrue(store.remove(sf1));
388 assertFalse(store.contains(sf1));
389 assertTrue(store.contains(sf2));
392 * re-add the 'nested' feature; it is now duplicated
395 assertEquals(store.size(), 2);
396 assertTrue(store.contains(sf2));
399 @Test(groups = "Functional")
400 public void testContains()
402 IntervalStore<SimpleFeature> store = new IntervalStore<>();
403 SimpleFeature sf1 = new SimpleFeature(10, 20, "Cath");
404 SimpleFeature sf2 = new SimpleFeature(10, 20, "Pfam");
407 assertTrue(store.contains(sf1));
408 assertTrue(store.contains(new SimpleFeature(sf1))); // identical feature
409 assertFalse(store.contains(sf2)); // different description
412 * add a nested feature
414 SimpleFeature sf3 = new SimpleFeature(12, 16, "Cath");
416 assertTrue(store.contains(sf3));
417 assertTrue(store.contains(new SimpleFeature(sf3)));
420 * delete the outer (enclosing, non-nested) feature
423 assertFalse(store.contains(sf1));
424 assertTrue(store.contains(sf3));
426 assertFalse(store.contains(null));
429 assertFalse(store.contains("junk"));
430 } catch (ClassCastException e)
436 @Test(groups = "Functional")
437 public void testFindOverlaps_resultsArg_mixed()
439 IntervalStore<SimpleFeature> store = new IntervalStore<>();
440 SimpleFeature sf1 = add(store, 10, 50);
441 SimpleFeature sf2 = add(store, 1, 15);
442 SimpleFeature sf3 = add(store, 20, 30);
443 SimpleFeature sf4 = add(store, 40, 100);
444 SimpleFeature sf5 = add(store, 60, 100);
445 SimpleFeature sf6 = add(store, 70, 70);
447 List<SimpleFeature> overlaps = new ArrayList<>();
448 List<SimpleFeature> overlaps2 = store.findOverlaps(200, 200, overlaps);
449 assertSame(overlaps, overlaps2);
450 assertTrue(overlaps.isEmpty());
453 store.findOverlaps(1, 9, overlaps);
454 assertEquals(overlaps.size(), 1);
455 assertTrue(overlaps.contains(sf2));
458 store.findOverlaps(5, 18, overlaps);
459 assertEquals(overlaps.size(), 2);
460 assertTrue(overlaps.contains(sf1));
461 assertTrue(overlaps.contains(sf2));
464 store.findOverlaps(30, 40, overlaps);
465 assertEquals(overlaps.size(), 3);
466 assertTrue(overlaps.contains(sf1));
467 assertTrue(overlaps.contains(sf3));
468 assertTrue(overlaps.contains(sf4));
471 store.findOverlaps(80, 90, overlaps);
472 assertEquals(overlaps.size(), 2);
473 assertTrue(overlaps.contains(sf4));
474 assertTrue(overlaps.contains(sf5));
477 store.findOverlaps(68, 70, overlaps);
478 assertEquals(overlaps.size(), 3);
479 assertTrue(overlaps.contains(sf4));
480 assertTrue(overlaps.contains(sf5));
481 assertTrue(overlaps.contains(sf6));
484 * and without clearing the list first
485 * note that sf4 is included twice, as an
486 * overlap of 68-70 and also of 30-40
488 store.findOverlaps(30, 40, overlaps);
489 assertEquals(overlaps.size(), 6);
490 assertTrue(overlaps.contains(sf1));
491 assertTrue(overlaps.contains(sf3));
492 assertTrue(overlaps.contains(sf4));
493 assertTrue(overlaps.contains(sf5));
494 assertTrue(overlaps.contains(sf6));
495 assertSame(sf4, overlaps.get(0));
496 assertSame(sf4, overlaps.get(4));
499 @Test(groups = "Functional")
500 public void testFindOverlaps_resultsArg_nested()
502 IntervalStore<SimpleFeature> store = new IntervalStore<>();
503 SimpleFeature sf1 = add(store, 10, 50);
504 SimpleFeature sf2 = add(store, 10, 40);
505 SimpleFeature sf3 = add(store, 20, 30);
506 // feature at same location but different description
507 SimpleFeature sf4 = new SimpleFeature(20, 30, "different desc");
509 SimpleFeature sf5 = add(store, 35, 36);
511 List<SimpleFeature> overlaps = new ArrayList<>();
512 store.findOverlaps(1, 9, overlaps);
513 assertTrue(overlaps.isEmpty());
515 store.findOverlaps(10, 15, overlaps);
516 assertEquals(overlaps.size(), 2);
517 assertTrue(overlaps.contains(sf1));
518 assertTrue(overlaps.contains(sf2));
521 store.findOverlaps(45, 60, overlaps);
522 assertEquals(overlaps.size(), 1);
523 assertTrue(overlaps.contains(sf1));
526 store.findOverlaps(32, 38, overlaps);
527 assertEquals(overlaps.size(), 3);
528 assertTrue(overlaps.contains(sf1));
529 assertTrue(overlaps.contains(sf2));
530 assertTrue(overlaps.contains(sf5));
533 store.findOverlaps(15, 25, overlaps);
534 assertEquals(overlaps.size(), 4);
535 assertTrue(overlaps.contains(sf1));
536 assertTrue(overlaps.contains(sf2));
537 assertTrue(overlaps.contains(sf3));
538 assertTrue(overlaps.contains(sf4));
541 @Test(groups = "Functional")
542 public void testFindOverlaps_resultsArg_nonNested()
544 IntervalStore<SimpleFeature> store = new IntervalStore<>();
545 SimpleFeature sf1 = add(store, 10, 20);
546 // same range different description
547 SimpleFeature sf2 = new SimpleFeature(10, 20, "desc");
549 SimpleFeature sf3 = add(store, 15, 25);
550 SimpleFeature sf4 = add(store, 20, 35);
552 assertEquals(store.size(), 4);
553 List<SimpleFeature> overlaps = new ArrayList<>();
554 store.findOverlaps(1, 9, overlaps);
555 assertTrue(overlaps.isEmpty());
557 store.findOverlaps(8, 10, overlaps);
558 assertEquals(overlaps.size(), 2);
559 assertTrue(overlaps.contains(sf1));
560 assertTrue(overlaps.contains(sf2));
563 store.findOverlaps(12, 16, overlaps);
564 assertEquals(overlaps.size(), 3);
565 assertTrue(overlaps.contains(sf1));
566 assertTrue(overlaps.contains(sf2));
567 assertTrue(overlaps.contains(sf3));
570 store.findOverlaps(33, 33, overlaps);
571 assertEquals(overlaps.size(), 1);
572 assertTrue(overlaps.contains(sf4));
575 * ensure edge cases are covered
578 store.findOverlaps(35, 40, overlaps);
579 assertEquals(overlaps.size(), 1);
580 assertTrue(overlaps.contains(sf4));
583 assertTrue(store.findOverlaps(36, 100, overlaps).isEmpty());
584 assertTrue(store.findOverlaps(1, 9, overlaps).isEmpty());
588 * Compares alternative IntervalStoreI implementations for time to add an
589 * interval then query, at a range of different scales of N (the number of
592 @Test(groups = "Timing")
593 public void testAddAndQueryTiming()
595 IntervalStoreI<SimpleFeature> store = new intervalstore.impl.IntervalStore<>();
596 System.out.println("IntervalStoreJ");
597 testAddAndQueryTiming(store);
599 System.out.println("\nnonc.IntervalStore");
600 store = new intervalstore.nonc.IntervalStore<>();
601 testAddAndQueryTiming(store);
605 * Populates the store with intervals, then logs the time taken to add an
606 * interval then query, at a range of different scales of N (the number of
611 private void testAddAndQueryTiming(IntervalStoreI<SimpleFeature> store)
613 final int seqlen = 100000; // e.g. length of gene sequence
614 final int repeats = 20;
616 final int warmups = 5;
617 final int[] scales = new int[] { 10 * K, 100 * K, K * K };
619 Random r = new Random(732); // fixed seed = repeatable test
620 int counter = 0; // to ensure a distinct description for each feature
622 System.out.println("Scale\titeration\tmicrosecs");
624 for (int scale : scales)
627 * add 'scale' features to the store
629 for (int i = 0; i < scale; i++)
631 SimpleFeature sf = makeFeature(seqlen, r, counter);
637 * do "add then query" and log the time taken for the query
638 * we ignore the first 5 repetitions as 'warmups'
641 for (int i = 0; i < repeats + warmups; i++)
643 SimpleFeature sf = makeFeature(seqlen, r, counter);
645 long t1 = System.nanoTime();
646 store.findOverlaps(10, 20);
647 long elapsed = System.nanoTime() - t1;
652 String.format("%d\t%d\t%d", scale, i - warmups,
656 System.out.println("average " + (total / (K * repeats)));
660 SimpleFeature makeFeature(final int seqlen, Random r, int counter)
662 int i1 = r.nextInt(seqlen);
663 int i2 = r.nextInt(seqlen);
664 int from = Math.min(i1, i2);
665 int to = Math.max(i1, i2);
666 SimpleFeature sf = new SimpleFeature(from, to, "desc" + counter);