From 3b799db61aa276a7c5191e260e61d690744de4e9 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 29 Jul 2011 13:23:17 -0400 Subject: [PATCH] RefMetaDataTracker cleanup and unit tests You know have to provide an explicit list of RODRecordLists upfront to the constructor. RefMetaDataTracker is now immutable. Changes in engine to incorporate these differences Extensive UnitTests for RefMetaDataTracker now. --- .../ManagingReferenceOrderedView.java | 10 +- .../datasources/providers/RodLocusView.java | 13 +- .../gatk/refdata/RefMetaDataTracker.java | 255 +++++++++------- .../refdata/RefMetaDataTrackerUnitTest.java | 275 ++++++++++++++++++ 4 files changed, 441 insertions(+), 112 deletions(-) create mode 100644 public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/ManagingReferenceOrderedView.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/ManagingReferenceOrderedView.java index f75f358e1..d065635c8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/ManagingReferenceOrderedView.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/ManagingReferenceOrderedView.java @@ -4,6 +4,7 @@ import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.refdata.utils.LocationAwareSeekableRODIterator; +import org.broadinstitute.sting.gatk.refdata.utils.RODRecordList; import org.broadinstitute.sting.utils.GenomeLoc; import java.util.ArrayList; @@ -51,10 +52,13 @@ public class ManagingReferenceOrderedView implements ReferenceOrderedView { * @return A tracker containing information about this locus. */ public RefMetaDataTracker getReferenceOrderedDataAtLocus( GenomeLoc loc, ReferenceContext referenceContext ) { - RefMetaDataTracker tracks = new RefMetaDataTracker(states.size(), referenceContext); + List bindings = states.isEmpty() ? Collections.emptyList() : new ArrayList(states.size()); + for ( ReferenceOrderedDataState state: states ) - tracks.bind( state.dataSource.getName(), state.iterator.seekForward(loc) ); - return tracks; + // todo -- warning, I removed the reference to the name from states + bindings.add( state.iterator.seekForward(loc) ); + + return new RefMetaDataTracker(bindings, referenceContext); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/RodLocusView.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/RodLocusView.java index 3db5bd19a..c38b09334 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/RodLocusView.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/RodLocusView.java @@ -96,21 +96,12 @@ public class RodLocusView extends LocusView implements ReferenceOrderedView { } rodQueue = new RODMergingIterator(iterators); - - //throw new StingException("RodLocusView currently disabled"); } public RefMetaDataTracker getReferenceOrderedDataAtLocus( GenomeLoc loc, ReferenceContext referenceContext ) { - RefMetaDataTracker t = new RefMetaDataTracker(allTracksHere.size(), referenceContext); - for ( RODRecordList track : allTracksHere ) { - if ( ! t.hasValues(track.getName()) ) - t.bind(track.getName(), track); - } - // special case the interval again -- add it into the ROD - if ( interval != null ) { t.bind(interval.getName(), interval); } - - return t; + if ( interval != null ) { allTracksHere.add(interval); } + return new RefMetaDataTracker(allTracksHere, referenceContext); } public boolean hasNext() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/RefMetaDataTracker.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/RefMetaDataTracker.java index 808ad5430..c47accb00 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/RefMetaDataTracker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/RefMetaDataTracker.java @@ -29,18 +29,13 @@ import java.util.*; * Time: 3:05:23 PM */ public class RefMetaDataTracker { + // TODO: this should be a list, not a map, actually + + private final static RODRecordList EMPTY_ROD_RECORD_LIST = new RODRecordListImpl("EMPTY"); + final Map map; final ReferenceContext ref; - protected static Logger logger = Logger.getLogger(RefMetaDataTracker.class); - - public RefMetaDataTracker(int nBindings, ReferenceContext ref) { - this.ref = ref; - if ( nBindings == 0 ) - map = Collections.emptyMap(); - else - map = new HashMap(nBindings); - } - + final protected static Logger logger = Logger.getLogger(RefMetaDataTracker.class); // ------------------------------------------------------------------------------------------ // @@ -50,18 +45,18 @@ public class RefMetaDataTracker { // // ------------------------------------------------------------------------------------------ - /** - * Binds the list of reference ordered data records (RMDs) to track name at this site. Should be used only by the traversal - * system to provide access to RMDs in a structured way to the walkers. - * - * DO NOT USE THIS FUNCTION UNLESS YOU ARE THE GATK ENGINE - * - * @param name the name of the track - * @param rod the collection of RMD data - */ - public void bind(final String name, RODRecordList rod) { - //logger.debug(String.format("Binding %s to %s", name, rod)); - map.put(canonicalName(name), maybeConvertToVariantContext(rod)); + public RefMetaDataTracker(final Collection allBindings, final ReferenceContext ref) { + this.ref = ref; + if ( allBindings.isEmpty() ) + map = Collections.emptyMap(); + else { + map = new HashMap(allBindings.size()); + for ( RODRecordList rod : allBindings ) { + //logger.debug(String.format("Binding %s to %s", name, rod)); + if ( rod != null ) + map.put(canonicalName(rod.getName()), maybeConvertToVariantContext(rod)); + } + } } /** @@ -81,24 +76,13 @@ public class RefMetaDataTracker { final VariantContext vc = VariantContextAdaptors.toVariantContext(bindings.getName(), rec.getUnderlyingObject(), ref); if ( vc != null ) // it's possible that the conversion failed, but we continue along anyway values.add(new GATKFeature.TribbleGATKFeature(ref.getGenomeLocParser(), vc, rec.getName())); - } + } else + values.add(rec); } return new RODRecordListImpl(bindings.getName(), values, bindings.getLocation()); } -// /** -// * Temporary setting for putting a reference context into the system. -// * -// * DO NOT USE THIS FUNCTION UNLESS YOU ARE THE GATK ENGINE -// * -// * @param ref -// */ -// public void setRef(final ReferenceContext ref) { -// this.ref = ref; -// } - - // ------------------------------------------------------------------------------------------ // // @@ -107,58 +91,47 @@ public class RefMetaDataTracker { // // ------------------------------------------------------------------------------------------ - /** - * No-assumption version of getValues(name, class). Returns Objects. - */ - public List getValues(final String name) { - return getValues(name, Object.class); + public List getValues(Class type) { + return addValues(map.keySet(), type, new ArrayList(), null, false, false); + } + public List getValues(Class type, final GenomeLoc onlyAtThisLoc) { + return addValues(map.keySet(), type, new ArrayList(), onlyAtThisLoc, true, false); + } + public List getValues(Class type, final String name) { + return addValues(name, type, new ArrayList(), getTrackDataByName(name), null, false, false); + } + public List getValues(Class type, final String name, final GenomeLoc onlyAtThisLoc) { + return addValues(name, type, new ArrayList(), getTrackDataByName(name), onlyAtThisLoc, true, false); + } + public List getValues(Class type, final Collection names) { + return addValues(names, type, new ArrayList(), null, false, false); + } + public List getValues(Class type, final Collection names, final GenomeLoc onlyAtThisLoc) { + return addValues(names, type, new ArrayList(), onlyAtThisLoc, true, false); } - /** - * get all the reference meta data associated with a track name. - * @param name the name of the track we're looking for - * @param clazz the expected class of the elements bound to rod name - * @return a list of objects, representing the underlying objects that the tracks produce. I.e. for a - * dbSNP RMD this will be a RodDbSNP, etc. - * - * Important: The list returned by this function is guaranteed not to be null, but may be empty! - */ - public List getValues(final String name, final Class clazz) { - RODRecordList list = getTrackDataByName(name); - - if (list == null) - return Collections.emptyList(); - else { - return addValues(name, clazz, new ArrayList(), list, list.getLocation(), false, false); - } + public T getFirstValue(Class type) { + return safeGetFirst(getValues(type)); + } + public T getFirstValue(Class type, final GenomeLoc onlyAtThisLoc) { + return safeGetFirst(getValues(type, onlyAtThisLoc)); + } + public T getFirstValue(Class type, final String name) { + return safeGetFirst(getValues(type, name)); + } + public T getFirstValue(Class type, final String name, final GenomeLoc onlyAtThisLoc) { + return safeGetFirst(getValues(type, name, onlyAtThisLoc)); + } + public T getFirstValue(Class type, final Collection names) { + return safeGetFirst(getValues(type, names)); + } + public T getFirstValue(Class type, final Collection names, final GenomeLoc onlyAtThisLoc) { + return safeGetFirst(getValues(type, names, onlyAtThisLoc)); } - - /** - * get a singleton record, given the name and a type. This function will return the first record at the - * current position seen. The object is cast into a type clazz, or thoses an error if this isn't possible. - * - * * WARNING: we now suppport more than one RMD at a single position for all tracks. If there are - * are multiple RMD objects at this location, there is no contract for which object this method will pick, and which object gets - * picked may change from time to time! BE WARNED! - * - * @param name the name of the track - * @param clazz the underlying type to return - * @param the type to parameterize on, matching the clazz argument - * @return a record of type T, or null if no record is present. - */ - public T getFirstValue(final String name, final Class clazz) { - RODRecordList objects = getTrackDataByName(name); - - // if empty or null return null; - if (objects == null || objects.size() < 1) return null; - - Object obj = objects.get(0).getUnderlyingObject(); - if (!(clazz.isAssignableFrom(obj.getClass()))) - throw new UserException.CommandLineException("Unable to case track named " + name + " to type of " + clazz.toString() - + " it's of type " + obj.getClass()); - else - return (T)obj; + final private T safeGetFirst(List l) { + // todo: should we be warning people here? Throwing an error? + return l.isEmpty() ? null : l.get(0); } /** @@ -195,8 +168,7 @@ public class RefMetaDataTracker { * Important: The list returned by this function is guaranteed not to be null, but may be empty! */ public List getValuesAsGATKFeatures(final String name) { - List feat = getTrackDataByName(name); - return (feat == null) ? new ArrayList() : feat; // to satisfy the above requirement that we don't return null + return getTrackDataByName(name); } /** @@ -209,7 +181,7 @@ public class RefMetaDataTracker { LinkedList bound = new LinkedList(); for ( RODRecordList value : map.values() ) { - if ( value != null && value.size() != 0 ) bound.add(value); + if ( value.size() != 0 ) bound.add(value); } return bound; @@ -222,13 +194,79 @@ public class RefMetaDataTracker { public int getNumberOfTracksWithValue() { int n = 0; for ( RODRecordList value : map.values() ) { - if ( value != null && ! value.isEmpty() ) { + if ( ! value.isEmpty() ) { n++; } } return n; } + // ------------------------------------------------------------------------------------------ + // + // + // old style Generic accessors + // + // TODO -- DELETE ME + // + // + // ------------------------------------------------------------------------------------------ + + /** + * No-assumption version of getValues(name, class). Returns Objects. + */ + @Deprecated + public List getValues(final String name) { + return getValues(name, Object.class); + } + + /** + * get all the reference meta data associated with a track name. + * @param name the name of the track we're looking for + * @param clazz the expected class of the elements bound to rod name + * @return a list of objects, representing the underlying objects that the tracks produce. I.e. for a + * dbSNP RMD this will be a RodDbSNP, etc. + * + * Important: The list returned by this function is guaranteed not to be null, but may be empty! + */ + @Deprecated + public List getValues(final String name, final Class clazz) { + RODRecordList list = getTrackDataByName(name); + + if (list.isEmpty()) + return Collections.emptyList(); + else { + return addValues(name, clazz, new ArrayList(), list, list.getLocation(), false, false); + } + } + + + /** + * get a singleton record, given the name and a type. This function will return the first record at the + * current position seen. The object is cast into a type clazz, or thoses an error if this isn't possible. + * + * * WARNING: we now suppport more than one RMD at a single position for all tracks. If there are + * are multiple RMD objects at this location, there is no contract for which object this method will pick, and which object gets + * picked may change from time to time! BE WARNED! + * + * @param name the name of the track + * @param clazz the underlying type to return + * @param the type to parameterize on, matching the clazz argument + * @return a record of type T, or null if no record is present. + */ + @Deprecated + public T getFirstValue(final String name, final Class clazz) { + RODRecordList objects = getTrackDataByName(name); + + if (objects.isEmpty()) return null; + + Object obj = objects.get(0).getUnderlyingObject(); + if (!(clazz.isAssignableFrom(obj.getClass()))) + throw new UserException.CommandLineException("Unable to case track named " + name + " to type of " + clazz.toString() + + " it's of type " + obj.getClass()); + else + return (T)obj; + } + // ------------------------------------------------------------------------------------------ // // @@ -244,6 +282,7 @@ public class RefMetaDataTracker { * * @return variant context */ + @Deprecated public List getAllVariantContexts() { return getAllVariantContexts(null, false, false); } @@ -254,6 +293,7 @@ public class RefMetaDataTracker { * @param curLocation * @return */ + @Deprecated public List getAllVariantContexts(final GenomeLoc curLocation) { return getAllVariantContexts(curLocation, true, false); } @@ -275,6 +315,7 @@ public class RefMetaDataTracker { * @param takeFirstOnly do we take the first rod only? * @return variant context */ + @Deprecated public List getAllVariantContexts(final GenomeLoc curLocation, final boolean requireStartHere, final boolean takeFirstOnly) { @@ -299,6 +340,7 @@ public class RefMetaDataTracker { * @param takeFirstOnly do we take the first rod only? * @return variant context */ + @Deprecated public List getVariantContexts(final String name, final GenomeLoc curLocation, final boolean requireStartHere, @@ -306,6 +348,7 @@ public class RefMetaDataTracker { return getVariantContexts(Arrays.asList(name), curLocation, requireStartHere, takeFirstOnly); } + @Deprecated public List getVariantContexts(final Collection names, final GenomeLoc curLocation, final boolean requireStartHere, @@ -314,9 +357,7 @@ public class RefMetaDataTracker { for ( String name : names ) { RODRecordList rodList = getTrackDataByName(name); // require that the name is an exact match - - if ( rodList != null ) - addVariantContexts(contexts, rodList, curLocation, requireStartHere, takeFirstOnly ); + addVariantContexts(contexts, rodList, curLocation, requireStartHere, takeFirstOnly ); } return contexts; @@ -332,6 +373,7 @@ public class RefMetaDataTracker { * @param requireStartHere do we require the rod to start at this location? * @return variant context */ + @Deprecated public VariantContext getVariantContext(final String name, final GenomeLoc curLocation, final boolean requireStartHere) { @@ -354,11 +396,13 @@ public class RefMetaDataTracker { * @param curLocation * @return */ + @Deprecated public VariantContext getVariantContext(final String name, final GenomeLoc curLocation) { return getVariantContext(name, curLocation, true); } + @Deprecated private void addVariantContexts(final List contexts, final RODRecordList rodList, final GenomeLoc curLocation, @@ -367,13 +411,27 @@ public class RefMetaDataTracker { addValues("xxx", VariantContext.class, contexts, rodList, curLocation, requireStartHere, takeFirstOnly); } - private static List addValues(final String name, - final Class type, - final List values, - final RODRecordList rodList, - final GenomeLoc curLocation, - final boolean requireStartHere, - final boolean takeFirstOnly ) { + private List addValues(final Collection names, + final Class type, + final List values, + final GenomeLoc curLocation, + final boolean requireStartHere, + final boolean takeFirstOnly ) { + for ( String name : names ) { + RODRecordList rodList = getTrackDataByName(name); // require that the name is an exact match + addValues(name, type, values, rodList, curLocation, requireStartHere, takeFirstOnly ); + } + + return values; + } + + private List addValues(final String name, + final Class type, + final List values, + final RODRecordList rodList, + final GenomeLoc curLocation, + final boolean requireStartHere, + final boolean takeFirstOnly ) { for ( GATKFeature rec : rodList ) { if ( ! requireStartHere || rec.getLocation().getStart() == curLocation.getStart() ) { // ok, we are going to keep this thing Object obj = rec.getUnderlyingObject(); @@ -406,7 +464,8 @@ public class RefMetaDataTracker { */ private RODRecordList getTrackDataByName(final String name) { final String luName = canonicalName(name); - return map.get(luName); + RODRecordList l = map.get(luName); + return l == null ? EMPTY_ROD_RECORD_LIST : l; } /** diff --git a/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java new file mode 100644 index 000000000..7ae1ed3be --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.refdata; + +import net.sf.samtools.SAMFileHeader; +import org.apache.log4j.Logger; +import org.broad.tribble.Feature; +import org.broad.tribble.dbsnp.DbSNPCodec; +import org.broad.tribble.dbsnp.DbSNPFeature; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.gatk.contexts.ReferenceContext; +import org.broadinstitute.sting.gatk.refdata.features.table.TableFeature; +import org.broadinstitute.sting.gatk.refdata.utils.GATKFeature; +import org.broadinstitute.sting.gatk.refdata.utils.RODRecordList; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.broadinstitute.sting.utils.variantcontext.Allele; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.testng.Assert; +import org.testng.annotations.*; +import java.util.*; +import java.util.List; + +public class RefMetaDataTrackerUnitTest { + final protected static Logger logger = Logger.getLogger(RefMetaDataTrackerUnitTest.class); + private static SAMFileHeader header; + private ReferenceContext context; + private GenomeLocParser genomeLocParser; + private GenomeLoc locus; + private final static int START_POS = 10; + Allele A,C,G,T; + VariantContext AC_SNP, AG_SNP, AT_SNP; + TableFeature span10_10, span1_20, span10_20; + DbSNPFeature dbsnp1, dbsnp2; + + @BeforeClass + public void beforeClass() { + header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 100); + genomeLocParser = new GenomeLocParser(header.getSequenceDictionary()); + locus = genomeLocParser.createGenomeLoc("chr1", START_POS, START_POS); + context = new ReferenceContext(genomeLocParser, locus, (byte)'A'); + A = Allele.create("A", true); + C = Allele.create("C"); + G = Allele.create("G"); + T = Allele.create("T"); + AC_SNP = new VariantContext("x", "chr1", START_POS, START_POS, Arrays.asList(A, C)); + AG_SNP = new VariantContext("x", "chr1", START_POS, START_POS, Arrays.asList(A, G)); + AT_SNP = new VariantContext("x", "chr1", START_POS, START_POS, Arrays.asList(A, T)); + span10_10 = makeSpan(10, 10); + span1_20 = makeSpan(1, 20); + span10_20 = makeSpan(10, 20); + + // dbsnp records + DbSNPCodec dbsnpCodec = new DbSNPCodec(); + String line1 = Utils.join("\t", "585 chr1 9 9 rs56289060 0 + - - -/C genomic insertion unknown 0 0 unknown between 1".split(" +")); + String line2 = Utils.join("\t", "585 chr1 9 10 rs55998931 0 + C C C/T genomic single unknown 0 0 unknown exact 1".split(" +")); + dbsnp1 = (DbSNPFeature)dbsnpCodec.decode(line1); + dbsnp2 = (DbSNPFeature)dbsnpCodec.decode(line2); + } + + private class MyTest extends BaseTest.TestDataProvider { + public RODRecordList AValues, BValues; + + private MyTest(Class c, final List AValues, final List BValues) { + super(c); + this.AValues = AValues == null ? null : makeRODRecord("A", AValues); + this.BValues = BValues == null ? null : makeRODRecord("B", BValues); + } + + private MyTest(final List AValues, final List BValues) { + super(MyTest.class); + this.AValues = AValues == null ? null : makeRODRecord("A", AValues); + this.BValues = BValues == null ? null : makeRODRecord("B", BValues); + } + + private final RODRecordList makeRODRecord(String name, List features) { + List x = new ArrayList(); + for ( Feature f : features ) + x.add(new GATKFeature.TribbleGATKFeature(genomeLocParser, f, name)); + return new RODRecordListImpl(name, x, locus); + } + + public List expected(String name) { + if ( name.equals("A+B") ) return allValues(); + if ( name.equals("A") ) return expectedAValues(); + if ( name.equals("B") ) return expectedBValues(); + throw new RuntimeException("FAIL"); + } + + public List allValues() { + List x = new ArrayList(); + x.addAll(expectedAValues()); + x.addAll(expectedBValues()); + return x; + } + + public List expectedAValues() { + return AValues == null ? Collections.emptyList() : AValues; + } + + public List expectedBValues() { + return BValues == null ? Collections.emptyList() : BValues; + } + + public RefMetaDataTracker makeTracker() { + List x = new ArrayList(); + if ( AValues != null ) x.add(AValues); + if ( BValues != null ) x.add(BValues); + return new RefMetaDataTracker(x, context); + } + + public int nBoundTracks() { + int n = 0; + if ( AValues != null ) n++; + if ( BValues != null ) n++; + return n; + } + } + + private class MyTestAdaptors extends MyTest { + private MyTestAdaptors(final List AValues) { + super(MyTestAdaptors.class, AValues, null); + } + } + + private final TableFeature makeSpan(int start, int stop) { + return new TableFeature(genomeLocParser.createGenomeLoc("chr1", start, stop), + Collections.emptyList(), Collections.emptyList()); + } + + @DataProvider(name = "tests") + public Object[][] createTests() { + new MyTest(null, null); + new MyTest(Arrays.asList(AC_SNP), null); + new MyTest(Arrays.asList(AC_SNP, AT_SNP), null); + new MyTest(Arrays.asList(AC_SNP), Arrays.asList(AG_SNP)); + new MyTest(Arrays.asList(AC_SNP, AT_SNP), Arrays.asList(AG_SNP)); + new MyTest(Arrays.asList(AC_SNP, AT_SNP), Arrays.asList(span10_10)); + new MyTest(Arrays.asList(AC_SNP, AT_SNP), Arrays.asList(span10_10, span10_20)); + new MyTest(Arrays.asList(AC_SNP, AT_SNP), Arrays.asList(span10_10, span10_20, span1_20)); + + // for requires starts + new MyTest(Arrays.asList(span1_20), null); + new MyTest(Arrays.asList(span10_10, span10_20), null); + new MyTest(Arrays.asList(span10_10, span10_20, span1_20), null); + + return MyTest.getTests(MyTest.class); + } + + @Test(enabled = true, dataProvider = "tests") + public void testRawBindings(MyTest test) { + logger.warn("Testing " + test + " for number of bound tracks"); + RefMetaDataTracker tracker = test.makeTracker(); + Assert.assertEquals(tracker.getNumberOfTracksWithValue(), test.nBoundTracks()); + + testSimpleBindings("A", tracker, test.AValues); + testSimpleBindings("B", tracker, test.BValues); + } + + private void testSimpleBindings(String name, RefMetaDataTracker tracker, RODRecordList expected) { + List asValues = tracker.getValues(Feature.class, name); + List asFeatures = tracker.getValuesAsGATKFeatures(name); + + Assert.assertEquals(tracker.hasValues(name), expected != null); + Assert.assertEquals(asFeatures.size(), expected == null ? 0 : expected.size()); + Assert.assertEquals(asValues.size(), expected == null ? 0 : expected.size()); + + if ( expected != null ) { + for ( GATKFeature e : expected ) { + boolean foundFeature = false; + for ( GATKFeature f : asFeatures ) { + if ( e.getUnderlyingObject() == f.getUnderlyingObject() ) foundFeature = true; + } + Assert.assertTrue(foundFeature, "Never found expected GATKFeature " + e + " bound to " + name + " in " + tracker); + + boolean foundValue = false; + for ( Feature f : asValues ) { + if ( e.getUnderlyingObject() == f ) foundValue = true; + } + Assert.assertTrue(foundValue, "Never found expected value of " + e.getUnderlyingObject() + " bound to " + name + " in " + tracker); + } + } + } + + @Test(enabled = true, dataProvider = "tests") + public void testGetters(MyTest test) { + logger.warn("Testing " + test + " for getFirst() methods"); + RefMetaDataTracker tracker = test.makeTracker(); + + for ( String name : Arrays.asList("A+B", "A", "B") ) { + List v1 = name.equals("A+B") ? tracker.getValues(Feature.class) : tracker.getValues(Feature.class, name); + testGetter(name, v1, test.expected(name), true, tracker); + + List v2 = name.equals("A+B") ? tracker.getValues(Feature.class, locus) : tracker.getValues(Feature.class, name, locus); + testGetter(name, v2, startingHere(test.expected(name)), true, tracker); + + Feature v3 = name.equals("A+B") ? tracker.getFirstValue(Feature.class) : tracker.getFirstValue(Feature.class, name); + testGetter(name, Arrays.asList(v3), test.expected(name), false, tracker); + + Feature v4 = name.equals("A+B") ? tracker.getFirstValue(Feature.class, locus) : tracker.getFirstValue(Feature.class, name, locus); + testGetter(name, Arrays.asList(v4), startingHere(test.expected(name)), false, tracker); + } + } + + private List startingHere(List l) { + List x = new ArrayList(); + for ( GATKFeature f : l ) if ( f.getStart() == locus.getStart() ) x.add(f); + return x; + } + + private void testGetter(String name, List got, List expected, boolean requireExact, RefMetaDataTracker tracker) { + if ( got.size() == 1 && got.get(0) == null ) + got = Collections.emptyList(); + + if ( requireExact ) + Assert.assertEquals(got.size(), expected.size()); + + boolean foundAny = false; + for ( GATKFeature e : expected ) { + boolean found1 = false; + for ( Feature got1 : got ) { + if ( e.getUnderlyingObject() == got1 ) + found1 = true; + } + if ( requireExact ) + Assert.assertTrue(found1, "Never found expected GATKFeature " + e + " bound to " + name + " in " + tracker); + foundAny = found1 || foundAny; + } + + if ( ! requireExact && ! expected.isEmpty() ) + Assert.assertTrue(foundAny, "Never found any got values matching one of the expected values bound to " + name + " in " + tracker); + } + + @Test(enabled = true, dataProvider = "testAdaptors") + public void testAdaptors(MyTestAdaptors test) { + logger.warn("Testing " + test + " for number of bound tracks"); + RefMetaDataTracker tracker = test.makeTracker(); + Assert.assertEquals(tracker.getNumberOfTracksWithValue(), test.nBoundTracks()); + + // all of the objects should be of type VariantContext + for ( Feature v : tracker.getValues(Feature.class) ) + Assert.assertEquals(v.getClass(), VariantContext.class, "Conversion failed from dbsnp to variant context in RefMetaDataTracker"); + } + + @DataProvider(name = "testAdaptors") + public Object[][] createTestAdaptors() { + new MyTestAdaptors(Arrays.asList(dbsnp1)); + new MyTestAdaptors(Arrays.asList(dbsnp1, dbsnp2)); + return MyTestAdaptors.getTests(MyTestAdaptors.class); + } +}