/* * Copyright (c) 2010 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; import junit.framework.Assert; import org.broad.tribble.Tribble; import org.broad.tribble.index.IndexFactory; import org.broad.tribble.vcf.VCFCodec; import org.broadinstitute.sting.gatk.CommandLineExecutable; import org.broadinstitute.sting.gatk.CommandLineGATK; import org.broadinstitute.sting.utils.GenomeLocParserTestUtils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.exceptions.StingException; import org.junit.Test; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.util.*; public class WalkerTest extends BaseTest { // the default output path for the integration test private File outputFileLocation = null; private static final boolean ENABLE_REPORTING = false; /** * Subdirectory under the ant build directory where we store integration test md5 results */ public static final String MD5_FILE_DB_SUBDIR = "integrationtests"; public void setOutputFileLocation(File outputFileLocation) { this.outputFileLocation = outputFileLocation; } private static void ensureMd5DbDirectory() { // todo -- make path File dir = new File(MD5_FILE_DB_SUBDIR); if ( ! dir.exists() ) { System.out.printf("##### Creating MD5 db %s%n", MD5_FILE_DB_SUBDIR); if ( ! dir.mkdir() ) { throw new ReviewedStingException("Infrastructure failure: failed to create md5 directory " + MD5_FILE_DB_SUBDIR); } } } private static File getFileForMD5(final String md5) { final String basename = String.format("%s.integrationtest", md5); return new File(MD5_FILE_DB_SUBDIR + "/" + basename); } private static void updateMD5Db(final String md5, final File resultsFile) { // todo -- copy results file to DB dir if needed under filename for md5 final File dbFile = getFileForMD5(md5); if ( ! dbFile.exists() ) { // the file isn't already in the db, copy it over System.out.printf("##### Updating MD5 file: %s%n", dbFile.getPath()); try { FileUtils.copyFile(resultsFile, dbFile); } catch ( IOException e ) { throw new ReviewedStingException(e.getMessage()); } } else { System.out.printf("##### MD5 file is up to date: %s%n", dbFile.getPath()); } } private static String getMD5Path(final String md5, final String valueIfNotFound) { // todo -- look up the result in the directory and return the path if it exists final File dbFile = getFileForMD5(md5); return dbFile.exists() ? dbFile.getPath() : valueIfNotFound; } public String assertMatchingMD5(final String name, final File resultsFile, final String expectedMD5) { try { byte[] bytesOfMessage = getBytesFromFile(resultsFile); byte[] thedigest = MessageDigest.getInstance("MD5").digest(bytesOfMessage); BigInteger bigInt = new BigInteger(1, thedigest); String filemd5sum = bigInt.toString(16); while (filemd5sum.length() < 32) filemd5sum = "0" + filemd5sum; // pad to length 32 // // copy md5 to integrationtests // updateMD5Db(filemd5sum, resultsFile); if (parameterize() || expectedMD5.equals("")) { System.out.println(String.format("PARAMETERIZATION[%s]: file %s has md5 = %s, stated expectation is %s, equal? = %b", name, resultsFile, filemd5sum, expectedMD5, filemd5sum.equals(expectedMD5))); } else { System.out.println(String.format("Checking MD5 for %s [calculated=%s, expected=%s]", resultsFile, filemd5sum, expectedMD5)); System.out.flush(); if ( ! expectedMD5.equals(filemd5sum) ) { // we are going to fail for real in assertEquals (so we are counted by the testing framework). // prepare ourselves for the comparison System.out.printf("##### Test %s is going fail #####%n", name); String pathToExpectedMD5File = getMD5Path(expectedMD5, "[No DB file found]"); String pathToFileMD5File = getMD5Path(filemd5sum, "[No DB file found]"); System.out.printf("##### Path to expected file (MD5=%s): %s%n", expectedMD5, pathToExpectedMD5File); System.out.printf("##### Path to calculated file (MD5=%s): %s%n", filemd5sum, pathToFileMD5File); System.out.printf("##### Diff command: diff %s %s%n", pathToExpectedMD5File, pathToFileMD5File); // todo -- add support for simple inline display of the first N differences for text file } Assert.assertEquals(name + " Mismatching MD5s", expectedMD5, filemd5sum); System.out.println(String.format(" => %s PASSED", name)); } return filemd5sum; } catch (Exception e) { throw new RuntimeException("Failed to read bytes from calls file: " + resultsFile, e); } } public void maybeValidateSupplementaryFile(final String name, final File resultFile) { File indexFile = Tribble.indexFile(resultFile); //System.out.println("Putative index file is " + indexFile); if ( indexFile.exists() ) { if ( resultFile.getAbsolutePath().contains(".vcf") ) { // todo -- currently we only understand VCF files! Blow up since we can't test them throw new StingException("Found an index created for file " + resultFile + " but we can only validate VCF files. Extend this code!"); } System.out.println("Verifying on-the-fly index " + indexFile + " for test " + name + " using file " + resultFile); Assert.assertTrue(IndexFactory.onDiskIndexEqualToNewlyCreatedIndex(resultFile, indexFile, new VCFCodec())); } } public List assertMatchingMD5s(final String name, List resultFiles, List expectedMD5s) { List md5s = new ArrayList(); for (int i = 0; i < resultFiles.size(); i++) { String md5 = assertMatchingMD5(name, resultFiles.get(i), expectedMD5s.get(i)); maybeValidateSupplementaryFile(name, resultFiles.get(i)); md5s.add(i, md5); } return md5s; } public static byte[] getBytesFromFile(File file) throws IOException { InputStream is = new FileInputStream(file); // Get the size of the file long length = file.length(); if (length > Integer.MAX_VALUE) { // File is too large } // Create the byte array to hold the data byte[] bytes = new byte[(int) length]; // Read in the bytes int offset = 0; int numRead = 0; while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { offset += numRead; } // Ensure all the bytes have been read in if (offset < bytes.length) { throw new IOException("Could not completely read file " + file.getName()); } // Close the input stream and return bytes is.close(); return bytes; } public class WalkerTestSpec { String args = ""; int nOutputFiles = -1; List md5s = null; List exts = null; Class expectedException = null; protected Map auxillaryFiles = new HashMap(); public WalkerTestSpec(String args, int nOutputFiles, List md5s) { this.args = args; this.nOutputFiles = nOutputFiles; this.md5s = md5s; } public WalkerTestSpec(String args, int nOutputFiles, List exts, List md5s) { this.args = args; this.nOutputFiles = nOutputFiles; this.md5s = md5s; this.exts = exts; } public WalkerTestSpec(String args, int nOutputFiles, Class expectedException) { this.args = args; this.nOutputFiles = nOutputFiles; this.expectedException = expectedException; } public boolean expectsException() { return expectedException != null; } public Class getExpectedException() { if ( ! expectsException() ) throw new ReviewedStingException("Tried to get expection for walker test that doesn't expect one"); return expectedException; } public void addAuxFile(String expectededMD5sum, File outputfile) { auxillaryFiles.put(expectededMD5sum, outputfile); } } protected boolean parameterize() { return false; } protected Pair, List> executeTestParallel(final String name, WalkerTestSpec spec) { return executeTest(name, spec, Arrays.asList(1, 4)); } protected Pair, List> executeTest(final String name, WalkerTestSpec spec, List parallelThreads) { String originalArgs = spec.args; Pair, List> results = null; for ( int nt : parallelThreads ) { String extra = nt == 1 ? "" : (" -nt " + nt); spec.args = originalArgs + extra; results = executeTest(name + "-nt-" + nt, spec); } return results; } protected Pair, List> executeTest(final String name, WalkerTestSpec spec) { ensureMd5DbDirectory(); // ensure the md5 directory exists List tmpFiles = new ArrayList(); for (int i = 0; i < spec.nOutputFiles; i++) { String ext = spec.exts == null ? ".tmp" : "." + spec.exts.get(i); File fl = createTempFile(String.format("walktest.tmp_param.%d", i), ext); tmpFiles.add(fl); } final String args = String.format(spec.args, tmpFiles.toArray()); System.out.println(Utils.dupString('-', 80)); if ( spec.expectsException() ) { // this branch handles the case were we are testing that a walker will fail as expected return executeTest(name, null, tmpFiles, args, spec.getExpectedException()); } else { List md5s = new LinkedList(); md5s.addAll(spec.md5s); // check to see if they included any auxillary files, if so add them to the list for (String md5 : spec.auxillaryFiles.keySet()) { md5s.add(md5); tmpFiles.add(spec.auxillaryFiles.get(md5)); } return executeTest(name, md5s, tmpFiles, args, null); } } public File createTempFile(String name, String extension) { try { File fl = File.createTempFile(name, extension); fl.deleteOnExit(); return fl; } catch (IOException ex) { throw new ReviewedStingException("Cannot create temp file: " + ex.getMessage(), ex); } } /** * execute the test, given the following: * @param name the name of the test * @param md5s the list of md5s * @param tmpFiles the temp file corresponding to the md5 list * @param args the argument list * @return a pair of file and string lists */ private Pair, List> executeTest(String name, List md5s, List tmpFiles, String args, Class expectedException) { GenomeLocParserTestUtils.clearSequenceDictionary(); CommandLineGATK instance = new CommandLineGATK(); String[] command; // special case for ' and " so we can allow expressions if (args.indexOf('\'') != -1) command = escapeExpressions(args, "'"); else if (args.indexOf('\"') != -1) command = escapeExpressions(args, "\""); else command = args.split(" "); if (outputFileLocation != null) { String[] cmd2 = Arrays.copyOf(command, command.length + 2); cmd2[command.length] = "-o"; cmd2[command.length + 1] = this.outputFileLocation.getAbsolutePath(); command = cmd2; } // add the logging level to each of the integration test commands String[] cmd2 = Arrays.copyOf(command, command.length + 4); cmd2[command.length] = "-l"; cmd2[command.length+1] = "WARN"; cmd2[command.length+2] = "-et"; cmd2[command.length+3] = ENABLE_REPORTING ? "STANDARD" : "NO_ET"; // run the executable boolean gotAnException = false; try { System.out.println(String.format("Executing test %s with GATK arguments: %s", name, Utils.join(" ",cmd2))); CommandLineExecutable.start(instance, cmd2); } catch (Exception e) { gotAnException = true; if ( expectedException != null ) { // we expect an exception System.out.println(String.format("Wanted exception %s, saw %s", expectedException, e.getClass())); if ( expectedException.isInstance(e) ) { // it's the type we expected System.out.println(String.format(" => %s PASSED", name)); } else { e.printStackTrace(); Assert.fail(String.format("Test %s expected exception %s but got %s instead", name, expectedException, e.getClass())); } } else { // we didn't expect an exception but we got one :-( throw new RuntimeException(e); } } // catch failures from the integration test if ( expectedException != null ) { if ( ! gotAnException ) // we expected an exception but didn't see it Assert.fail(String.format("Test %s expected exception %s but none was thrown", name, expectedException.toString())); return null; } else { if ( CommandLineExecutable.result != 0) { throw new RuntimeException("Error running the GATK with arguments: " + args); } // clean up some memory instance = null; cmd2 = null; // we need to check MD5s return new Pair, List>(tmpFiles, assertMatchingMD5s(name, tmpFiles, md5s)); } } private static String[] escapeExpressions(String args, String delimiter) { String[] command = {}; String[] split = args.split(delimiter); for (int i = 0; i < split.length - 1; i += 2) { command = Utils.concatArrays(command, split[i].trim().split(" ")); command = Utils.concatArrays(command, new String[]{split[i + 1]}); } return Utils.concatArrays(command, split[split.length - 1].trim().split(" ")); } @Test public void testWalkerUnitTest() { //System.out.println("WalkerTest is just a framework"); } }