1 package jalview.datamodel.features;
2 import static org.testng.Assert.assertEquals;
3 import static org.testng.Assert.assertFalse;
4 import static org.testng.Assert.assertSame;
5 import static org.testng.Assert.assertTrue;
7 import jalview.datamodel.ContiguousI;
8 import jalview.datamodel.Range;
9 import jalview.datamodel.SequenceFeature;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.Comparator;
14 import java.util.List;
15 import java.util.Random;
17 import junit.extensions.PA;
19 import org.testng.annotations.DataProvider;
20 import org.testng.annotations.Test;
22 public class NCListTest
25 private Random random = new Random(107);
27 private Comparator<ContiguousI> sorter = new RangeComparator(true);
30 * A basic sanity test of the constructor
32 @Test(groups = "Functional")
33 public void testConstructor()
35 List<Range> ranges = new ArrayList<Range>();
36 ranges.add(new Range(20, 20));
37 ranges.add(new Range(10, 20));
38 ranges.add(new Range(15, 30));
39 ranges.add(new Range(10, 30));
40 ranges.add(new Range(11, 19));
41 ranges.add(new Range(10, 20));
42 ranges.add(new Range(1, 100));
44 NCList<Range> ncl = new NCList<Range>(ranges);
45 String expected = "[1-100 [10-30 [10-20 [10-20 [11-19]]]], 15-30 [20-20]]";
46 assertEquals(ncl.toString(), expected);
47 assertTrue(ncl.isValid());
49 Collections.reverse(ranges);
50 ncl = new NCList<Range>(ranges);
51 assertEquals(ncl.toString(), expected);
52 assertTrue(ncl.isValid());
55 @Test(groups = "Functional")
56 public void testFindOverlaps()
58 List<Range> ranges = new ArrayList<Range>();
59 ranges.add(new Range(20, 50));
60 ranges.add(new Range(30, 70));
61 ranges.add(new Range(1, 100));
62 ranges.add(new Range(70, 120));
64 NCList<Range> ncl = new NCList<Range>(ranges);
66 List<Range> overlaps = ncl.findOverlaps(121, 122);
67 assertEquals(overlaps.size(), 0);
69 overlaps = ncl.findOverlaps(21, 22);
70 assertEquals(overlaps.size(), 2);
71 assertEquals(((ContiguousI) overlaps.get(0)).getBegin(), 1);
72 assertEquals(((ContiguousI) overlaps.get(0)).getEnd(), 100);
73 assertEquals(((ContiguousI) overlaps.get(1)).getBegin(), 20);
74 assertEquals(((ContiguousI) overlaps.get(1)).getEnd(), 50);
76 overlaps = ncl.findOverlaps(110, 110);
77 assertEquals(overlaps.size(), 1);
78 assertEquals(((ContiguousI) overlaps.get(0)).getBegin(), 70);
79 assertEquals(((ContiguousI) overlaps.get(0)).getEnd(), 120);
82 @Test(groups = "Functional")
83 public void testAdd_onTheEnd()
85 List<Range> ranges = new ArrayList<Range>();
86 ranges.add(new Range(20, 50));
87 NCList<Range> ncl = new NCList<Range>(ranges);
88 assertEquals(ncl.toString(), "[20-50]");
89 assertTrue(ncl.isValid());
91 ncl.add(new Range(60, 70));
92 assertEquals(ncl.toString(), "[20-50, 60-70]");
93 assertTrue(ncl.isValid());
96 @Test(groups = "Functional")
97 public void testAdd_inside()
99 List<Range> ranges = new ArrayList<Range>();
100 ranges.add(new Range(20, 50));
101 NCList<Range> ncl = new NCList<Range>(ranges);
102 assertEquals(ncl.toString(), "[20-50]");
103 assertTrue(ncl.isValid());
105 ncl.add(new Range(30, 40));
106 assertEquals(ncl.toString(), "[20-50 [30-40]]");
109 @Test(groups = "Functional")
110 public void testAdd_onTheFront()
112 List<Range> ranges = new ArrayList<Range>();
113 ranges.add(new Range(20, 50));
114 NCList<Range> ncl = new NCList<Range>(ranges);
115 assertEquals(ncl.toString(), "[20-50]");
116 assertTrue(ncl.isValid());
118 ncl.add(new Range(5, 15));
119 assertEquals(ncl.toString(), "[5-15, 20-50]");
120 assertTrue(ncl.isValid());
123 @Test(groups = "Functional")
124 public void testAdd_enclosing()
126 List<Range> ranges = new ArrayList<Range>();
127 ranges.add(new Range(20, 50));
128 ranges.add(new Range(30, 60));
129 NCList<Range> ncl = new NCList<Range>(ranges);
130 assertEquals(ncl.toString(), "[20-50, 30-60]");
131 assertTrue(ncl.isValid());
132 assertEquals(ncl.getStart(), 20);
134 ncl.add(new Range(10, 70));
135 assertEquals(ncl.toString(), "[10-70 [20-50, 30-60]]");
136 assertTrue(ncl.isValid());
139 @Test(groups = "Functional")
140 public void testAdd_spanning()
142 List<Range> ranges = new ArrayList<Range>();
143 ranges.add(new Range(20, 40));
144 ranges.add(new Range(60, 70));
145 NCList<Range> ncl = new NCList<Range>(ranges);
146 assertEquals(ncl.toString(), "[20-40, 60-70]");
147 assertTrue(ncl.isValid());
149 ncl.add(new Range(30, 50));
150 assertEquals(ncl.toString(), "[20-40, 30-50, 60-70]");
151 assertTrue(ncl.isValid());
153 ncl.add(new Range(40, 65));
154 assertEquals(ncl.toString(), "[20-40, 30-50, 40-65, 60-70]");
155 assertTrue(ncl.isValid());
159 * Provides the scales for pseudo-random NCLists i.e. the range of the maximal
160 * [0-scale] interval to be stored
164 @DataProvider(name = "scalesOfLife")
165 public Object[][] getScales()
167 return new Object[][] { new Integer[] { 10 }, new Integer[] { 100 } };
171 * Do a number of pseudo-random (reproducible) builds of an NCList, to
172 * exercise as many methods of the class as possible while generating the
173 * range of possible structure topologies
175 * <li>verify that add adds an entry and increments size</li>
176 * <li>...except where the entry is already contained (by equals test)</li>
177 * <li>verify that the structure is valid at all stages of construction</li>
178 * <li>generate, run and verify a range of overlap queries</li>
179 * <li>tear down the structure by deleting entries, verifying correctness at
183 @Test(groups = "Functional", dataProvider = "scalesOfLife")
184 public void test_pseudoRandom(Integer scale)
186 NCList<SequenceFeature> ncl = new NCList<SequenceFeature>();
187 List<SequenceFeature> features = new ArrayList<SequenceFeature>(scale);
189 testAdd_pseudoRandom(scale, ncl, features);
192 * sort the list of added ranges - this doesn't affect the test,
193 * just makes it easier to inspect the data in the debugger
195 Collections.sort(features, sorter);
197 testFindOverlaps_pseudoRandom(ncl, scale, features);
199 testDelete_pseudoRandom(ncl, features);
203 * Pick randomly selected entries to delete in turn, checking the NCList size
204 * and validity at each stage, until it is empty
209 protected void testDelete_pseudoRandom(NCList<SequenceFeature> ncl,
210 List<SequenceFeature> features)
214 while (!features.isEmpty())
216 assertEquals(ncl.size(), features.size());
217 int toDelete = random.nextInt(features.size());
218 SequenceFeature entry = features.get(toDelete);
219 assertTrue(ncl.contains(entry), String.format(
220 "NCList doesn't contain entry [%d] '%s'!", deleted,
224 assertFalse(ncl.contains(entry), String.format(
225 "NCList still contains deleted entry [%d] '%s'!", deleted,
227 features.remove(toDelete);
230 assertTrue(ncl.isValid(), String.format(
231 "NCList invalid after %d deletions, last deleted was '%s'",
232 deleted, entry.toString()));
235 * brute force check that deleting one entry didn't delete any others
237 for (int i = 0; i < features.size(); i++)
239 SequenceFeature sf = features.get(i);
240 assertTrue(ncl.contains(sf), String.format(
241 "NCList doesn't contain entry [%d] %s after deleting '%s'!",
242 i, sf.toString(), entry.toString()));
245 assertEquals(ncl.size(), 0); // all gone
249 * Randomly generate entries and add them to the NCList, checking its validity
250 * and size at each stage. A few entries should be duplicates (by equals test)
257 protected void testAdd_pseudoRandom(Integer scale,
258 NCList<SequenceFeature> ncl,
259 List<SequenceFeature> features)
264 for (int i = 0; i < size; i++)
266 int r1 = random.nextInt(scale + 1);
267 int r2 = random.nextInt(scale + 1);
268 int from = Math.min(r1, r2);
269 int to = Math.max(r1, r2);
272 * choice of two feature values means that occasionally an identical
273 * feature may be generated, in which case it should not be added
275 float value = (float) i % 2;
276 SequenceFeature feature = new SequenceFeature("Pfam", "", from, to,
280 * add to NCList - with duplicate entries (by equals) disallowed
282 ncl.add(feature, false);
283 if (features.contains(feature))
285 System.out.println("Duplicate feature generated "
286 + feature.toString());
290 features.add(feature);
295 * check list format is valid at each stage of its construction
297 assertTrue(ncl.isValid(),
298 String.format("Failed for scale = %d, i=%d", scale, i));
299 assertEquals(ncl.size(), count);
301 // System.out.println(ncl.prettyPrint());
305 * A helper method that generates pseudo-random range queries and veries that
306 * findOverlaps returns the correct matches
309 * the NCList to query
311 * ncl maximal range is [0, scale]
313 * a list of the ranges stored in ncl
315 protected void testFindOverlaps_pseudoRandom(NCList<SequenceFeature> ncl,
317 List<SequenceFeature> features)
319 int halfScale = scale / 2;
320 int minIterations = 20;
323 * generates ranges in [-halfScale, scale+halfScale]
324 * - some should be internal to [0, scale] P = 1/4
325 * - some should lie before 0 P = 1/16
326 * - some should lie after scale P = 1/16
327 * - some should overlap left P = 1/4
328 * - some should overlap right P = 1/4
329 * - some should enclose P = 1/8
331 * 50 iterations give a 96% probability of including the
332 * unlikeliest case; keep going until we have done all!
334 boolean inside = false;
335 boolean enclosing = false;
336 boolean before = false;
337 boolean after = false;
338 boolean overlapLeft = false;
339 boolean overlapRight = false;
340 boolean allCasesCovered = false;
343 while (i < minIterations || !allCasesCovered)
346 int r1 = random.nextInt((scale + 1) * 2);
347 int r2 = random.nextInt((scale + 1) * 2);
348 int from = Math.min(r1, r2) - halfScale;
349 int to = Math.max(r1, r2) - halfScale;
352 * ensure all cases of interest get covered
354 inside |= from >= 0 && to <= scale;
355 enclosing |= from <= 0 && to >= scale;
357 after |= from > scale;
358 overlapLeft |= from < 0 && to >= 0 && to <= scale;
359 overlapRight |= from >= 0 && from <= scale && to > scale;
360 if (!allCasesCovered)
362 allCasesCovered |= inside && enclosing && before && after
363 && overlapLeft && overlapRight;
368 .format("Covered all findOverlaps cases after %d iterations for scale %d",
373 verifyFindOverlaps(ncl, from, to, features);
378 * A helper method that verifies that overlaps found by interrogating an
379 * NCList correctly match those found by brute force search
386 protected void verifyFindOverlaps(NCList<SequenceFeature> ncl, int from,
387 int to, List<SequenceFeature> features)
389 List<SequenceFeature> overlaps = ncl.findOverlaps(from, to);
392 * check returned entries do indeed overlap from-to range
394 for (ContiguousI sf : overlaps)
396 int begin = sf.getBegin();
397 int end = sf.getEnd();
398 assertTrue(begin <= to && end >= from, String.format(
399 "[%d, %d] does not overlap query range [%d, %d]", begin, end,
404 * check overlapping ranges are included in the results
405 * (the test above already shows non-overlapping ranges are not)
407 for (ContiguousI sf : features)
409 int begin = sf.getBegin();
410 int end = sf.getEnd();
411 if (begin <= to && end >= from)
413 boolean found = overlaps.contains(sf);
414 assertTrue(found, String.format(
415 "[%d, %d] missing in query range [%d, %d]", begin, end,
421 @Test(groups = "Functional")
422 public void testGetEntries()
424 List<Range> ranges = new ArrayList<Range>();
425 Range r1 = new Range(20, 20);
426 Range r2 = new Range(10, 20);
427 Range r3 = new Range(15, 30);
428 Range r4 = new Range(10, 30);
429 Range r5 = new Range(11, 19);
430 Range r6 = new Range(10, 20);
438 NCList<Range> ncl = new NCList<Range>(ranges);
439 Range r7 = new Range(1, 100);
442 List<Range> contents = ncl.getEntries();
443 assertEquals(contents.size(), 7);
444 assertTrue(contents.contains(r1));
445 assertTrue(contents.contains(r2));
446 assertTrue(contents.contains(r3));
447 assertTrue(contents.contains(r4));
448 assertTrue(contents.contains(r5));
449 assertTrue(contents.contains(r6));
450 assertTrue(contents.contains(r7));
452 ncl = new NCList<Range>();
453 assertTrue(ncl.getEntries().isEmpty());
456 @Test(groups = "Functional")
457 public void testDelete()
459 List<Range> ranges = new ArrayList<Range>();
460 Range r1 = new Range(20, 30);
462 NCList<Range> ncl = new NCList<Range>(ranges);
463 assertTrue(ncl.getEntries().contains(r1));
465 Range r2 = new Range(20, 30);
466 assertFalse(ncl.delete(null)); // null argument
467 assertFalse(ncl.delete(r2)); // never added
468 assertTrue(ncl.delete(r1)); // success
469 assertTrue(ncl.getEntries().isEmpty());
472 * tests where object.equals() == true
474 NCList<SequenceFeature> features = new NCList<SequenceFeature>();
475 SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
477 SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
480 assertEquals(sf1, sf2); // sf1.equals(sf2)
481 assertFalse(features.delete(sf2)); // equality is not enough for deletion
482 assertTrue(features.getEntries().contains(sf1)); // still there!
483 assertTrue(features.delete(sf1));
484 assertTrue(features.getEntries().isEmpty()); // gone now
487 * test with duplicate objects in NCList
491 assertEquals(features.getEntries().size(), 2);
492 assertSame(features.getEntries().get(0), sf1);
493 assertSame(features.getEntries().get(1), sf1);
494 assertTrue(features.delete(sf1)); // first match only is deleted
495 assertTrue(features.contains(sf1));
496 assertEquals(features.size(), 1);
497 assertTrue(features.delete(sf1));
498 assertTrue(features.getEntries().isEmpty());
501 @Test(groups = "Functional")
502 public void testAdd_overlapping()
504 List<Range> ranges = new ArrayList<Range>();
505 ranges.add(new Range(40, 50));
506 ranges.add(new Range(20, 30));
507 NCList<Range> ncl = new NCList<Range>(ranges);
508 assertEquals(ncl.toString(), "[20-30, 40-50]");
509 assertTrue(ncl.isValid());
512 * add range overlapping internally
514 ncl.add(new Range(25, 35));
515 assertEquals(ncl.toString(), "[20-30, 25-35, 40-50]");
516 assertTrue(ncl.isValid());
519 * add range overlapping last range
521 ncl.add(new Range(45, 55));
522 assertEquals(ncl.toString(), "[20-30, 25-35, 40-50, 45-55]");
523 assertTrue(ncl.isValid());
526 * add range overlapping first range
528 ncl.add(new Range(15, 25));
529 assertEquals(ncl.toString(), "[15-25, 20-30, 25-35, 40-50, 45-55]");
530 assertTrue(ncl.isValid());
534 * Test the contains method (which uses object equals test)
536 @Test(groups = "Functional")
537 public void testContains()
539 NCList<SequenceFeature> ncl = new NCList<SequenceFeature>();
540 SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
542 SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
544 SequenceFeature sf3 = new SequenceFeature("type", "desc", 1, 10, 2f,
548 assertTrue(ncl.contains(sf1));
549 assertTrue(ncl.contains(sf2)); // sf1.equals(sf2)
550 assertFalse(ncl.contains(sf3)); // !sf1.equals(sf3)
553 * make some deeper structure in the NCList
555 SequenceFeature sf4 = new SequenceFeature("type", "desc", 2, 9, 2f,
558 assertTrue(ncl.contains(sf4));
559 SequenceFeature sf5 = new SequenceFeature("type", "desc", 4, 5, 2f,
561 SequenceFeature sf6 = new SequenceFeature("type", "desc", 6, 8, 2f,
565 assertTrue(ncl.contains(sf5));
566 assertTrue(ncl.contains(sf6));
569 @Test(groups = "Functional")
570 public void testIsValid()
572 List<Range> ranges = new ArrayList<Range>();
573 Range r1 = new Range(40, 50);
575 NCList<Range> ncl = new NCList<Range>(ranges);
576 assertTrue(ncl.isValid());
578 Range r2 = new Range(42, 44);
580 assertTrue(ncl.isValid());
581 Range r3 = new Range(46, 48);
583 assertTrue(ncl.isValid());
584 Range r4 = new Range(43, 43);
586 assertTrue(ncl.isValid());
588 assertEquals(ncl.toString(), "[40-50 [42-44 [43-43], 46-48]]");
589 assertTrue(ncl.isValid());
591 PA.setValue(r1, "start", 43);
592 assertFalse(ncl.isValid()); // r2 not inside r1
593 PA.setValue(r1, "start", 40);
594 assertTrue(ncl.isValid());
596 PA.setValue(r3, "start", 41);
597 assertFalse(ncl.isValid()); // r3 should precede r2
598 PA.setValue(r3, "start", 46);
599 assertTrue(ncl.isValid());
601 PA.setValue(r4, "start", 41);
602 assertFalse(ncl.isValid()); // r4 not inside r2
603 PA.setValue(r4, "start", 43);
604 assertTrue(ncl.isValid());
606 PA.setValue(r4, "start", 44);
607 assertFalse(ncl.isValid()); // r4 has reverse range
610 @Test(groups = "Functional")
611 public void testPrettyPrint()
614 * construct NCList from a list of ranges
615 * they are sorted then assembled into NCList subregions
616 * notice that 42-42 end up inside 41-46
618 List<Range> ranges = new ArrayList<Range>();
619 ranges.add(new Range(40, 50));
620 ranges.add(new Range(45, 55));
621 ranges.add(new Range(40, 45));
622 ranges.add(new Range(41, 46));
623 ranges.add(new Range(42, 42));
624 ranges.add(new Range(42, 42));
625 NCList<Range> ncl = new NCList<Range>(ranges);
626 assertTrue(ncl.isValid());
627 assertEquals(ncl.toString(),
628 "[40-50 [40-45], 41-46 [42-42 [42-42]], 45-55]");
629 String expected = "40-50\n 40-45\n41-46\n 42-42\n 42-42\n45-55\n";
630 assertEquals(ncl.prettyPrint(), expected);
633 * repeat but now add ranges one at a time
634 * notice that 42-42 end up inside 40-50 so we get
635 * a different but equal valid NCList structure
638 ncl = new NCList<Range>(ranges);
639 ncl.add(new Range(40, 50));
640 ncl.add(new Range(45, 55));
641 ncl.add(new Range(40, 45));
642 ncl.add(new Range(41, 46));
643 ncl.add(new Range(42, 42));
644 ncl.add(new Range(42, 42));
645 assertTrue(ncl.isValid());
646 assertEquals(ncl.toString(),
647 "[40-50 [40-45 [42-42 [42-42]], 41-46], 45-55]");
648 expected = "40-50\n 40-45\n 42-42\n 42-42\n 41-46\n45-55\n";
649 assertEquals(ncl.prettyPrint(), expected);
653 * A test that shows different valid trees can be constructed from the same
654 * set of ranges, depending on the order of construction
656 @Test(groups = "Functional")
657 public void testConstructor_alternativeTrees()
659 List<Range> ranges = new ArrayList<Range>();
660 ranges.add(new Range(10, 60));
661 ranges.add(new Range(20, 30));
662 ranges.add(new Range(40, 50));
665 * constructor with greedy traversal of sorted ranges to build nested
666 * containment lists results in 20-30 inside 10-60, 40-50 a sibling
668 NCList<Range> ncl = new NCList<Range>(ranges);
669 assertEquals(ncl.toString(), "[10-60 [20-30], 40-50]");
670 assertTrue(ncl.isValid());
673 * adding ranges one at a time results in 40-50
674 * a sibling of 20-30 inside 10-60
676 ncl = new NCList<Range>(new Range(10, 60));
677 ncl.add(new Range(20, 30));
678 ncl.add(new Range(40, 50));
679 assertEquals(ncl.toString(), "[10-60 [20-30, 40-50]]");
680 assertTrue(ncl.isValid());