X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=test%2Fjalview%2Fdatamodel%2Ffeatures%2FNCListTest.java;h=2c7f752d43fe19c8039f722d4e63b467077ba9c5;hb=92389e0816624da2c0530b0a7477c70f8da6ec4d;hp=ca4288cd6c77fc33ba1b612d574a57bffe89c8e9;hpb=41cee8a4b26678d0d4c5e42e852a0b50cbc9bb2e;p=jalview.git diff --git a/test/jalview/datamodel/features/NCListTest.java b/test/jalview/datamodel/features/NCListTest.java index ca4288c..2c7f752 100644 --- a/test/jalview/datamodel/features/NCListTest.java +++ b/test/jalview/datamodel/features/NCListTest.java @@ -1,13 +1,21 @@ package jalview.datamodel.features; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; +import jalview.datamodel.ContiguousI; +import jalview.datamodel.Range; +import jalview.datamodel.SequenceFeature; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Random; +import junit.extensions.PA; + import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -160,45 +168,137 @@ public class NCListTest } /** - * Do a number of pseudo-random (reproducible) builds of an NCList, with - * checks for validity of the data structure, and searches for (pseudo-random) - * overlap ranges, for greater (but not perfect!) confidence that corner cases - * are being handled correctly. + * Do a number of pseudo-random (reproducible) builds of an NCList, to + * exercise as many methods of the class as possible while generating the + * range of possible structure topologies + * */ @Test(groups = "Functional", dataProvider = "scalesOfLife") - public void testAdd_FindOverlaps_pseudoRandom(Integer scale) + public void test_pseudoRandom(Integer scale) { - NCList ncl = new NCList(); - int count = 0; - List ranges = new ArrayList(scale); + NCList ncl = new NCList(); + List features = new ArrayList(scale); - for (int i = 0; i < scale; i++) + testAdd_pseudoRandom(scale, ncl, features); + + /* + * sort the list of added ranges - this doesn't affect the test, + * just makes it easier to inspect the data in the debugger + */ + Collections.sort(features, sorter); + + testFindOverlaps_pseudoRandom(ncl, scale, features); + + testDelete_pseudoRandom(ncl, features); + } + + /** + * Pick randomly selected entries to delete in turn, checking the NCList size + * and validity at each stage, until it is empty + * + * @param ncl + * @param features + */ + protected void testDelete_pseudoRandom(NCList ncl, + List features) + { + int deleted = 0; + + while (!features.isEmpty()) + { + assertEquals(ncl.size(), features.size()); + int toDelete = random.nextInt(features.size()); + SequenceFeature entry = features.get(toDelete); + assertTrue(ncl.contains(entry), String.format( + "NCList doesn't contain entry [%d] '%s'!", deleted, + entry.toString())); + + ncl.delete(entry); + assertFalse(ncl.contains(entry), String.format( + "NCList still contains deleted entry [%d] '%s'!", deleted, + entry.toString())); + features.remove(toDelete); + deleted++; + + assertTrue(ncl.isValid(), String.format( + "NCList invalid after %d deletions, last deleted was '%s'", + deleted, entry.toString())); + + /* + * brute force check that deleting one entry didn't delete any others + */ + for (int i = 0; i < features.size(); i++) + { + SequenceFeature sf = features.get(i); + assertTrue(ncl.contains(sf), String.format( + "NCList doesn't contain entry [%d] %s after deleting '%s'!", + i, sf.toString(), entry.toString())); + } + } + assertEquals(ncl.size(), 0); // all gone + } + + /** + * Randomly generate entries and add them to the NCList, checking its validity + * and size at each stage. A few entries should be duplicates (by equals test) + * so not get added. + * + * @param scale + * @param ncl + * @param features + */ + protected void testAdd_pseudoRandom(Integer scale, + NCList ncl, + List features) + { + int count = 0; + final int size = 50; + + for (int i = 0; i < size; i++) { int r1 = random.nextInt(scale + 1); int r2 = random.nextInt(scale + 1); int from = Math.min(r1, r2); int to = Math.max(r1, r2); - Range range = new Range(from, to); - ncl.add(range); - ranges.add(range); + + /* + * choice of two feature values means that occasionally an identical + * feature may be generated, in which case it should not be added + */ + float value = (float) i % 2; + SequenceFeature feature = new SequenceFeature("Pfam", "", from, to, + value, "group"); + + /* + * add to NCList - with duplicate entries (by equals) disallowed + */ + ncl.add(feature, false); + if (features.contains(feature)) + { + System.out.println("Duplicate feature generated " + + feature.toString()); + } + else + { + features.add(feature); + count++; + } /* * check list format is valid at each stage of its construction */ assertTrue(ncl.isValid(), String.format("Failed for scale = %d, i=%d", scale, i)); - count++; - assertEquals(ncl.getSize(), count); + assertEquals(ncl.size(), count); } // System.out.println(ncl.prettyPrint()); - - /* - * sort the list of added ranges - this doesn't affect the test, - * just makes it easier to inspect the data in the debugger - */ - Collections.sort(ranges, sorter); - - testFindOverlaps(ncl, scale, ranges); } /** @@ -209,11 +309,12 @@ public class NCListTest * the NCList to query * @param scale * ncl maximal range is [0, scale] - * @param ranges + * @param features * a list of the ranges stored in ncl */ - protected void testFindOverlaps(NCList ncl, int scale, - List ranges) + protected void testFindOverlaps_pseudoRandom(NCList ncl, + int scale, + List features) { int halfScale = scale / 2; int minIterations = 20; @@ -269,7 +370,7 @@ public class NCListTest } } - verifyFindOverlaps(ncl, from, to, ranges); + verifyFindOverlaps(ncl, from, to, features); } } @@ -280,20 +381,20 @@ public class NCListTest * @param ncl * @param from * @param to - * @param ranges + * @param features */ - protected void verifyFindOverlaps(NCList ncl, int from, int to, - List ranges) + protected void verifyFindOverlaps(NCList ncl, int from, + int to, List features) { - List overlaps = ncl.findOverlaps(from, to); + List overlaps = ncl.findOverlaps(from, to); /* * check returned entries do indeed overlap from-to range */ - for (Range r : overlaps) + for (ContiguousI sf : overlaps) { - int begin = r.getBegin(); - int end = r.getEnd(); + int begin = sf.getBegin(); + int end = sf.getEnd(); assertTrue(begin <= to && end >= from, String.format( "[%d, %d] does not overlap query range [%d, %d]", begin, end, from, to)); @@ -303,13 +404,13 @@ public class NCListTest * check overlapping ranges are included in the results * (the test above already shows non-overlapping ranges are not) */ - for (Range r : ranges) + for (ContiguousI sf : features) { - int begin = r.getBegin(); - int end = r.getEnd(); + int begin = sf.getBegin(); + int end = sf.getEnd(); if (begin <= to && end >= from) { - boolean found = overlaps.contains(r); + boolean found = overlaps.contains(sf); assertTrue(found, String.format( "[%d, %d] missing in query range [%d, %d]", begin, end, from, to)); @@ -355,7 +456,46 @@ public class NCListTest @Test(groups = "Functional") public void testDelete() { - assertTrue(false, "todo"); + List ranges = new ArrayList(); + Range r1 = new Range(20, 30); + ranges.add(r1); + NCList ncl = new NCList(ranges); + assertTrue(ncl.getEntries().contains(r1)); + + Range r2 = new Range(20, 30); + assertFalse(ncl.delete(null)); // null argument + assertFalse(ncl.delete(r2)); // never added + assertTrue(ncl.delete(r1)); // success + assertTrue(ncl.getEntries().isEmpty()); + + /* + * tests where object.equals() == true + */ + NCList features = new NCList(); + SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f, + "group"); + SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f, + "group"); + features.add(sf1); + assertEquals(sf1, sf2); // sf1.equals(sf2) + assertFalse(features.delete(sf2)); // equality is not enough for deletion + assertTrue(features.getEntries().contains(sf1)); // still there! + assertTrue(features.delete(sf1)); + assertTrue(features.getEntries().isEmpty()); // gone now + + /* + * test with duplicate objects in NCList + */ + features.add(sf1); + features.add(sf1); + assertEquals(features.getEntries().size(), 2); + assertSame(features.getEntries().get(0), sf1); + assertSame(features.getEntries().get(1), sf1); + assertTrue(features.delete(sf1)); // first match only is deleted + assertTrue(features.contains(sf1)); + assertEquals(features.size(), 1); + assertTrue(features.delete(sf1)); + assertTrue(features.getEntries().isEmpty()); } @Test(groups = "Functional") @@ -389,4 +529,154 @@ public class NCListTest assertEquals(ncl.toString(), "[15-25, 20-30, 25-35, 40-50, 45-55]"); assertTrue(ncl.isValid()); } + + /** + * Test the contains method (which uses object equals test) + */ + @Test(groups = "Functional") + public void testContains() + { + NCList ncl = new NCList(); + SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f, + "group"); + SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f, + "group"); + SequenceFeature sf3 = new SequenceFeature("type", "desc", 1, 10, 2f, + "anothergroup"); + ncl.add(sf1); + + assertTrue(ncl.contains(sf1)); + assertTrue(ncl.contains(sf2)); // sf1.equals(sf2) + assertFalse(ncl.contains(sf3)); // !sf1.equals(sf3) + + /* + * make some deeper structure in the NCList + */ + SequenceFeature sf4 = new SequenceFeature("type", "desc", 2, 9, 2f, + "group"); + ncl.add(sf4); + assertTrue(ncl.contains(sf4)); + SequenceFeature sf5 = new SequenceFeature("type", "desc", 4, 5, 2f, + "group"); + SequenceFeature sf6 = new SequenceFeature("type", "desc", 6, 8, 2f, + "group"); + ncl.add(sf5); + ncl.add(sf6); + assertTrue(ncl.contains(sf5)); + assertTrue(ncl.contains(sf6)); + } + + @Test(groups = "Functional") + public void testIsValid() + { + List ranges = new ArrayList(); + Range r1 = new Range(40, 50); + ranges.add(r1); + NCList ncl = new NCList(ranges); + assertTrue(ncl.isValid()); + + Range r2 = new Range(42, 44); + ncl.add(r2); + assertTrue(ncl.isValid()); + Range r3 = new Range(46, 48); + ncl.add(r3); + assertTrue(ncl.isValid()); + Range r4 = new Range(43, 43); + ncl.add(r4); + assertTrue(ncl.isValid()); + + assertEquals(ncl.toString(), "[40-50 [42-44 [43-43], 46-48]]"); + assertTrue(ncl.isValid()); + + PA.setValue(r1, "start", 43); + assertFalse(ncl.isValid()); // r2 not inside r1 + PA.setValue(r1, "start", 40); + assertTrue(ncl.isValid()); + + PA.setValue(r3, "start", 41); + assertFalse(ncl.isValid()); // r3 should precede r2 + PA.setValue(r3, "start", 46); + assertTrue(ncl.isValid()); + + PA.setValue(r4, "start", 41); + assertFalse(ncl.isValid()); // r4 not inside r2 + PA.setValue(r4, "start", 43); + assertTrue(ncl.isValid()); + + PA.setValue(r4, "start", 44); + assertFalse(ncl.isValid()); // r4 has reverse range + } + + @Test(groups = "Functional") + public void testPrettyPrint() + { + /* + * construct NCList from a list of ranges + * they are sorted then assembled into NCList subregions + * notice that 42-42 end up inside 41-46 + */ + List ranges = new ArrayList(); + ranges.add(new Range(40, 50)); + ranges.add(new Range(45, 55)); + ranges.add(new Range(40, 45)); + ranges.add(new Range(41, 46)); + ranges.add(new Range(42, 42)); + ranges.add(new Range(42, 42)); + NCList ncl = new NCList(ranges); + assertTrue(ncl.isValid()); + assertEquals(ncl.toString(), + "[40-50 [40-45], 41-46 [42-42 [42-42]], 45-55]"); + String expected = "40-50\n 40-45\n41-46\n 42-42\n 42-42\n45-55\n"; + assertEquals(ncl.prettyPrint(), expected); + + /* + * repeat but now add ranges one at a time + * notice that 42-42 end up inside 40-50 so we get + * a different but equal valid NCList structure + */ + ranges.clear(); + ncl = new NCList(ranges); + ncl.add(new Range(40, 50)); + ncl.add(new Range(45, 55)); + ncl.add(new Range(40, 45)); + ncl.add(new Range(41, 46)); + ncl.add(new Range(42, 42)); + ncl.add(new Range(42, 42)); + assertTrue(ncl.isValid()); + assertEquals(ncl.toString(), + "[40-50 [40-45 [42-42 [42-42]], 41-46], 45-55]"); + expected = "40-50\n 40-45\n 42-42\n 42-42\n 41-46\n45-55\n"; + assertEquals(ncl.prettyPrint(), expected); + } + + /** + * A test that shows different valid trees can be constructed from the same + * set of ranges, depending on the order of construction + */ + @Test(groups = "Functional") + public void testConstructor_alternativeTrees() + { + List ranges = new ArrayList(); + ranges.add(new Range(10, 60)); + ranges.add(new Range(20, 30)); + ranges.add(new Range(40, 50)); + + /* + * constructor with greedy traversal of sorted ranges to build nested + * containment lists results in 20-30 inside 10-60, 40-50 a sibling + */ + NCList ncl = new NCList(ranges); + assertEquals(ncl.toString(), "[10-60 [20-30], 40-50]"); + assertTrue(ncl.isValid()); + + /* + * adding ranges one at a time results in 40-50 + * a sibling of 20-30 inside 10-60 + */ + ncl = new NCList(new Range(10, 60)); + ncl.add(new Range(20, 30)); + ncl.add(new Range(40, 50)); + assertEquals(ncl.toString(), "[10-60 [20-30, 40-50]]"); + assertTrue(ncl.isValid()); + } }