package jalview.datamodel.features;
-
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Random;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class NCListTest
{
+ private Random random = new Random(107);
+
+ private Comparator<ContiguousI> sorter = new RangeComparator(true);
+
/**
* A basic sanity test of the constructor
*/
}
/**
+ * Provides the scales for pseudo-random NCLists i.e. the range of the maximal
+ * [0-scale] interval to be stored
+ *
+ * @return
+ */
+ @DataProvider(name = "scalesOfLife")
+ public Object[][] getScales()
+ {
+ return new Object[][] { new Integer[] { 10 }, new Integer[] { 100 } };
+ }
+
+ /**
* Do a number of pseudo-random (reproducible) builds of an NCList, with
- * checks for validity of the data structure, for greater (but not perfect!)
- * confidence that corner cases are being handled correctly.
+ * 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.
*/
- @Test(groups = "Functional")
- public void testAdd_pseudoRandom()
+ @Test(groups = "Functional", dataProvider = "scalesOfLife")
+ public void testAdd_FindOverlaps_pseudoRandom(Integer scale)
{
- Random r = new Random(108); // well why not?
- int[] scales = new int[] { 10, 100, 1000 };
+ NCList<Range> ncl = new NCList<Range>();
+ int count = 0;
+ List<Range> ranges = new ArrayList<Range>(scale);
+
+ for (int i = 0; i < scale; 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);
+
+ /*
+ * 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);
+ }
+ // 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);
+ }
- for (int scale : scales)
+ /**
+ * A helper method that generates pseudo-random range queries and veries that
+ * findOverlaps returns the correct matches
+ *
+ * @param ncl
+ * the NCList to query
+ * @param scale
+ * ncl maximal range is [0, scale]
+ * @param ranges
+ * a list of the ranges stored in ncl
+ */
+ protected void testFindOverlaps(NCList<Range> ncl, int scale,
+ List<Range> ranges)
+ {
+ int halfScale = scale / 2;
+ int minIterations = 20;
+
+ /*
+ * generates ranges in [-halfScale, scale+halfScale]
+ * - some should be internal to [0, scale] P = 1/4
+ * - some should lie before 0 P = 1/16
+ * - some should lie after scale P = 1/16
+ * - some should overlap left P = 1/4
+ * - some should overlap right P = 1/4
+ * - some should enclose P = 1/8
+ *
+ * 50 iterations give a 96% probability of including the
+ * unlikeliest case; keep going until we have done all!
+ */
+ boolean inside = false;
+ boolean enclosing = false;
+ boolean before = false;
+ boolean after = false;
+ boolean overlapLeft = false;
+ boolean overlapRight = false;
+ boolean allCasesCovered = false;
+
+ int i = 0;
+ while (i < minIterations || !allCasesCovered)
{
- NCList<Range> ncl = new NCList<Range>();
- int size = 0;
- for (int i = 0; i < 100; i++)
+ i++;
+ int r1 = random.nextInt((scale + 1) * 2);
+ int r2 = random.nextInt((scale + 1) * 2);
+ int from = Math.min(r1, r2) - halfScale;
+ int to = Math.max(r1, r2) - halfScale;
+
+ /*
+ * ensure all cases of interest get covered
+ */
+ inside |= from >= 0 && to <= scale;
+ enclosing |= from <= 0 && to >= scale;
+ before |= to < 0;
+ after |= from > scale;
+ overlapLeft |= from < 0 && to >= 0 && to <= scale;
+ overlapRight |= from >= 0 && from <= scale && to > scale;
+ if (!allCasesCovered)
{
- int r1 = r.nextInt(scale);
- int r2 = r.nextInt(scale);
- int nextFrom = Math.min(r1, r2);
- int nextTo = Math.max(r1, r2);
- Range range = new Range(nextFrom, nextTo);
- ncl.add(range);
- assertTrue(ncl.isValid(),
- String.format("Failed for scale = %d, i=%d", scale, i));
- size++;
- assertEquals(ncl.getSize(), size);
+ allCasesCovered |= inside && enclosing && before && after
+ && overlapLeft && overlapRight;
+ if (allCasesCovered)
+ {
+ System.out
+ .println(String
+ .format("Covered all findOverlaps cases after %d iterations for scale %d",
+ i, scale));
+ }
+ }
+
+ verifyFindOverlaps(ncl, from, to, ranges);
+ }
+ }
+
+ /**
+ * A helper method that verifies that overlaps found by interrogating an
+ * NCList correctly match those found by brute force search
+ *
+ * @param ncl
+ * @param from
+ * @param to
+ * @param ranges
+ */
+ protected void verifyFindOverlaps(NCList<Range> ncl, int from, int to,
+ List<Range> ranges)
+ {
+ List<Range> overlaps = ncl.findOverlaps(from, to);
+
+ /*
+ * check returned entries do indeed overlap from-to range
+ */
+ for (Range r : overlaps)
+ {
+ int begin = r.getBegin();
+ int end = r.getEnd();
+ assertTrue(begin <= to && end >= from, String.format(
+ "[%d, %d] does not overlap query range [%d, %d]", begin, end,
+ from, to));
+ }
+
+ /*
+ * check overlapping ranges are included in the results
+ * (the test above already shows non-overlapping ranges are not)
+ */
+ for (Range r : ranges)
+ {
+ int begin = r.getBegin();
+ int end = r.getEnd();
+ if (begin <= to && end >= from)
+ {
+ boolean found = overlaps.contains(r);
+ assertTrue(found, String.format(
+ "[%d, %d] missing in query range [%d, %d]", begin, end,
+ from, to));
}
- System.out.println(ncl.prettyPrint());
}
}
ncl = new NCList<Range>();
assertTrue(ncl.getEntries().isEmpty());
}
+
+ @Test(groups = "Functional")
+ public void testDelete()
+ {
+ assertTrue(false, "todo");
+ }
+
+ @Test(groups = "Functional")
+ public void testAdd_overlapping()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(40, 50));
+ ranges.add(new Range(20, 30));
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), "[20-30, 40-50]");
+ assertTrue(ncl.isValid());
+
+ /*
+ * add range overlapping internally
+ */
+ ncl.add(new Range(25, 35));
+ assertEquals(ncl.toString(), "[20-30, 25-35, 40-50]");
+ assertTrue(ncl.isValid());
+
+ /*
+ * add range overlapping last range
+ */
+ ncl.add(new Range(45, 55));
+ assertEquals(ncl.toString(), "[20-30, 25-35, 40-50, 45-55]");
+ assertTrue(ncl.isValid());
+
+ /*
+ * add range overlapping first range
+ */
+ ncl.add(new Range(15, 25));
+ assertEquals(ncl.toString(), "[15-25, 20-30, 25-35, 40-50, 45-55]");
+ assertTrue(ncl.isValid());
+ }
}