Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 385   Methods: 8
NCLOC: 193   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
JUnitTestManager.java 70.6% 85.1% 100% 82.8%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.model.junit;
 38   
 39    import junit.framework.*;
 40   
 41    import java.io.File;
 42    import java.util.Enumeration;
 43    import java.util.List;
 44    import java.util.ArrayList;
 45    import java.util.Arrays;
 46   
 47    import edu.rice.cs.util.Log;
 48    import edu.rice.cs.util.StringOps;
 49    import edu.rice.cs.util.UnexpectedException;
 50    import edu.rice.cs.util.swing.Utilities;
 51    import edu.rice.cs.util.classloader.ClassFileError;
 52    import edu.rice.cs.plt.io.IOUtil;
 53    import edu.rice.cs.plt.lambda.Lambda;
 54    import edu.rice.cs.plt.tuple.Pair;
 55    import edu.rice.cs.plt.iter.IterUtil;
 56    import edu.rice.cs.plt.reflect.ShadowingClassLoader;
 57   
 58    import java.lang.reflect.Modifier;
 59   
 60    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 61    import static edu.rice.cs.plt.debug.DebugUtil.error;
 62   
 63    import edu.rice.cs.drjava.model.compiler.LanguageLevelStackTraceMapper;
 64   
 65    /** Runs in the InterpreterJVM. Runs tests given a classname and formats the results into a (serializable) array of
 66    * JUnitError that can be passed back to the MainJVM.
 67    * @version $Id: JUnitTestManager.java 5437 2011-08-05 03:48:19Z rcartwright $
 68    */
 69    public class JUnitTestManager {
 70   
 71    protected static final Log _log = new Log("JUnitTestManager.txt", false);
 72   
 73    /** The interface to the master JVM via RMI. */
 74    private final JUnitModelCallback _jmc;
 75   
 76    /** A factory producing a ClassLoader for tests with the given parent */
 77    private final Lambda<ClassLoader, ClassLoader> _loaderFactory;
 78   
 79    /** The current testRunner; initially null. Each test suite requires a new runner. */
 80    private JUnitTestRunner _testRunner;
 81   
 82    /** The accumulated test suite; null if no test is pending. */
 83    private TestSuite _suite = null;
 84   
 85    /** The accumulated list of names of TestCase classes; null if no test is pending. */
 86    private List<String> _testClassNames = null;
 87   
 88    /** The list of files corresponding to testClassNames; null if no test is pending. */
 89    private List<File> _testFiles = null;
 90   
 91    /** Standard constructor */
 92  180 public JUnitTestManager(JUnitModelCallback jmc, Lambda<ClassLoader, ClassLoader> loaderFactory) {
 93  180 _jmc = jmc;
 94  180 _loaderFactory = loaderFactory;
 95    }
 96   
 97    /** Find the test classes among the given classNames and accumulate them in
 98    * TestSuite for junit. Returns null if a test suite is already pending.
 99    * @param classNames the class names that are test class candidates
 100    * @param files the files corresponding to classNames
 101    */
 102  18 public List<String> findTestClasses(final List<String> classNames, final List<File> files) {
 103    // debug.logStart(new String[]{"classNames", "files"}, classNames, files);
 104  18 _log.log("findTestClasses(" + classNames + ", " + files + ")");
 105   
 106  18 if (_testClassNames != null && ! _testClassNames.isEmpty())
 107  0 throw new IllegalStateException("Test suite is still pending!");
 108   
 109  18 _testRunner = makeRunner();
 110   
 111  18 _testClassNames = new ArrayList<String>();
 112  18 _testFiles = new ArrayList<File>();
 113  18 _suite = new TestSuite();
 114   
 115  18 for (Pair<String, File> pair : IterUtil.zip(classNames, files)) {
 116  20 String cName = pair.first();
 117  20 try {
 118  20 if (_isJUnitTest(_testRunner.loadPossibleTest(cName))) {
 119  16 _testClassNames.add(cName);
 120  16 _testFiles.add(pair.second());
 121  16 _suite.addTest(new JUnit4TestAdapter(_testRunner.loadPossibleTest(cName)));
 122    }
 123    }
 124  0 catch (ClassNotFoundException e) { error.log(e); }
 125    catch(LinkageError e) {
 126    //debug.log(e);
 127  0 String path = IOUtil.attemptAbsoluteFile(pair.second()).getPath();
 128  0 _jmc.classFileError(new ClassFileError(cName, path, e));
 129    }
 130    }
 131   
 132    // debug.logEnd("result", _testClassNames);
 133  18 _log.log("returning: " + _testClassNames);
 134  18 return _testClassNames;
 135    }
 136   
 137    /** Runs the pending test suite set up by the preceding call to findTestClasses. Runs in a single auxiliary thread,
 138    * so no need for explicit synchronization.
 139    * @return false if no test suite (even an empty one) has been set up
 140    */
 141  16 @SuppressWarnings("unchecked")
 142    public /* synchronized */ boolean runTestSuite() {
 143   
 144  16 _log.log("runTestSuite() called");
 145   
 146  0 if (_testClassNames == null || _testClassNames.isEmpty()) return false;
 147   
 148    // Utilities.show("runTestSuite() in SlaveJVM called");
 149   
 150  16 try {
 151    // System.err.println("Calling _testRunner.runSuite(...)");
 152  16 TestResult result = _testRunner.runSuite(_suite);
 153   
 154  15 JUnitError[] errors = new JUnitError[result.errorCount() + result.failureCount()];
 155   
 156  15 Enumeration<TestFailure> failures = result.failures();
 157  15 Enumeration<TestFailure> errEnum = result.errors();
 158   
 159  15 int i = 0;
 160   
 161  15 while (errEnum.hasMoreElements()) {
 162  13 TestFailure tErr = errEnum.nextElement();
 163    // Utilities.show("Processing error " + tErr);
 164  13 errors[i] = _makeJUnitError(tErr, _testClassNames, true, _testFiles);
 165  13 i++;
 166    }
 167    // Utilities.show("Finished processing errors");
 168  15 while (failures.hasMoreElements()) {
 169  0 TestFailure tFail = failures.nextElement();
 170    // Utilities.show("Processing failure " + tFail);
 171  0 errors[i] = _makeJUnitError(tFail, _testClassNames, false, _testFiles);
 172  0 i++;
 173    }
 174    // new ScrollableDialog(null, "Slave JVM: testSuite ended with errors", "", Arrays.toString(errors)).show();
 175    // Utilities.show("Finished processing failures");
 176    // Utilities.show("errors = " + Arrays.toString(errors));
 177   
 178  15 _reset();
 179  15 _jmc.testSuiteEnded(errors);
 180    }
 181    catch(Exception e) {
 182  0 JUnitError[] errors = new JUnitError[1];
 183  0 errors[0] = new JUnitError(null, -1, -1, e.getMessage(), false, "", "", e.toString(), e.getStackTrace());
 184  0 _reset();
 185  0 _jmc.testSuiteEnded(errors);
 186    // new ScrollableDialog(null, "Slave JVM: testSuite ended with errors", "", Arrays.toString(errors)).show();
 187    }
 188  15 _log.log("Exiting runTestSuite()");
 189  15 return true;
 190    }
 191   
 192  15 private void _reset() {
 193  15 _suite = null;
 194  15 _testClassNames = null;
 195  15 _testFiles = null;
 196  15 _log.log("test manager state reset");
 197    }
 198   
 199   
 200   
 201    /** Determines if the given class is a junit Test.
 202    * @param c the class to check
 203    * @return true iff the given class is an instance of junit.framework.Test
 204    */
 205  20 private boolean _isJUnitTest(Class<?> c) {
 206   
 207  20 boolean result = (Test.class.isAssignableFrom(c) && !Modifier.isAbstract(c.getModifiers()) && !Modifier.isInterface(c.getModifiers()) ||
 208    (new JUnit4TestAdapter(c).getTests().size()>0)) && !new JUnit4TestAdapter(c).getTests().get(0).toString().contains("initializationError")
 209    ; //had to add specific check for initializationError. Is there a better way of checking if a class contains a test?
 210  20 debug.logValues(new String[]{"c", "isJUnitTest(c)"}, c, result);
 211  20 return result;
 212    }
 213   
 214    /** Constructs a new JUnitError from a TestFailure
 215    * @param failure A given TestFailure
 216    * @param classNames The classes that were used for this test suite
 217    * @param isError The passed TestFailure may signify either an error or a failure
 218    * @param files The files that were used for this test suite
 219    * @return JUnitError
 220    */
 221  13 private JUnitError _makeJUnitError(TestFailure failure, List<String> classNames, boolean isError, List<File> files) {
 222   
 223    // Utilities.show("_makeJUnitError called with failure " + failure + " failedTest = " + failure.failedTest());
 224  13 Test failedTest = failure.failedTest();
 225  13 String testName;
 226    /*if (failedTest instanceof TestCase) testName = ((TestCase)failedTest).getName();
 227  13 else */ if(failedTest instanceof JUnit4TestCaseFacade)
 228    {
 229  13 testName = ((JUnit4TestCaseFacade) failedTest).toString();
 230  13 testName = testName.substring(0,testName.indexOf('(')); //shaves off the class from TestName string
 231    }
 232  0 else testName = failedTest.getClass().getName();
 233   
 234  13 String testString = failure.toString();
 235  13 int firstIndex = testString.indexOf('(') + 1;
 236  13 int secondIndex = testString.indexOf(')');
 237   
 238    /** junit can return a string in two different formats; we parse both formats, and then decide which one to use. */
 239   
 240  13 String className;
 241  13 if (firstIndex != secondIndex)
 242  13 className = testString.substring(firstIndex, secondIndex);
 243    else
 244  0 className = testString.substring(0, firstIndex-1);
 245   
 246   
 247  13 String classNameAndTest = className + "." + testName;
 248    // Utilities.show("classNameAndTest = " + classNameAndTest);
 249  13 String exception = failure.thrownException().toString();
 250  13 StackTraceElement[] stackTrace = failure.thrownException().getStackTrace();
 251   
 252    /* Check to see if the class and test name appear directly in the stack trace. If
 253    * they don't, then we'll have to do additional work to find the line number. Additionally,
 254    * if the exception occured in a subclass of the test class, we'll need to adjust our conception
 255    * of the class name.
 256    */
 257  13 StringBuilder sb = new StringBuilder();
 258  13 sb.append(exception);
 259  13 sb.append('\n');
 260  13 for(StackTraceElement s: stackTrace) {
 261  453 sb.append("\tat ");
 262  453 sb.append(s);
 263    }
 264  13 String combined = sb.toString();
 265  13 int lineNum = -1;
 266   
 267  13 if (combined.indexOf(classNameAndTest) == -1) {
 268    /* get the stack trace of the junit error */
 269  4 String trace = failure.trace();
 270    /* knock off the first line of the stack trace.
 271    * now the string will look like
 272    * at my.package.class(file.java:line)
 273    * at other.package.class(anotherfile.java:line)
 274    * etc...
 275    */
 276  4 trace = trace.substring(trace.indexOf('\n')+1);
 277  4 if (trace.trim().length()>0) {
 278  4 while (trace.indexOf("junit.framework.Assert") != -1 &&
 279    trace.indexOf("junit.framework.Assert") < trace.indexOf("(")) {
 280    /* the format of the trace will have "at junit.framework.Assert..."
 281    * on each line until the line of the actual source file.
 282    * if the exception was thrown from the test case (so the test failed
 283    * without going through assert), then the source file will be on
 284    * the first line of the stack trace
 285    */
 286  5 trace = trace.substring(trace.indexOf('\n') + 1);
 287    }
 288  4 trace = trace.substring(trace.indexOf('(')+1);
 289  4 trace = trace.substring(0, trace.indexOf(')'));
 290    // If the exception occurred in a subclass of the test class, then update our
 291    // concept of the class and test name. Otherwise, we're only here to pick up the
 292    // line number.
 293  4 if (combined.indexOf(className) == -1) {
 294  3 int dotPos = trace.lastIndexOf('.');
 295  3 if (dotPos!=-1) {
 296  3 className = trace.substring(0,dotPos);
 297  3 classNameAndTest = className + "." + testName;
 298    }
 299    }
 300   
 301  4 try {
 302  4 lineNum = Integer.parseInt(trace.substring(trace.indexOf(':') + 1)) - 1;
 303    }
 304  0 catch (NumberFormatException e) { lineNum = 0; } // may be native method
 305    }
 306    }
 307   
 308  13 if (lineNum < 0) {
 309  9 lineNum = _lineNumber(combined, classNameAndTest);
 310    }
 311   
 312    // if (lineNum > -1) _errorsWithPos++;
 313   
 314  13 String message = (isError) ? failure.thrownException().toString():
 315    failure.thrownException().getMessage();
 316   
 317  13 boolean isFailure = (failure.thrownException() instanceof AssertionError || failure.thrownException() instanceof AssertionFailedError) &&
 318    !classNameAndTest.equals("junit.framework.TestSuite$1.warning");
 319   
 320    // for debugging
 321    // try{
 322    // File temp = File.createTempFile("asdf", "java", new File("/home/awulf"));
 323    // FileWriter writer = new FileWriter(temp);
 324    // writer.write("testString: " + testString + "\n");
 325    // writer.write("old className: " + className1 + "\n");
 326    // writer.write("new className: " + className2 + "\n");
 327    // writer.write("file: " + file + "\n");
 328    // writer.write("lineNum: " + lineNum + "\n");
 329    // writer.write("exception: " + exception + "\n");
 330    // writer.write("!isFailure: " + !isFailure + "\n");
 331    // writer.write("testName: " + testName + "\n");
 332    // writer.write("className: " + className + "\n");
 333    // writer.write("stackTrace: " + stackTrace + "\n");
 334    // writer.close();
 335    // } catch(IOException e) {
 336    //
 337    // }
 338   
 339  13 int indexOfClass = classNames.indexOf(className);
 340  13 File file;
 341  9 if (indexOfClass != -1) file = files.get(indexOfClass);
 342  4 else file = _jmc.getFileForClassName(className);
 343   
 344    // if testClass contains no
 345   
 346    // a test didn't fail, we couldn't even open the test.
 347  13 if (file == null) {
 348  0 return new JUnitError(new File("nofile"), 0, 0, message, !isFailure, testName, className, exception, stackTrace);
 349    }
 350   
 351  13 return new JUnitError(file, lineNum, 0, message, !isFailure, testName, className, exception, stackTrace);
 352    }
 353   
 354    /** Parses the line number out of the stack trace in the given class name. */
 355  9 private int _lineNumber(String sw, String classname) {
 356    // TODO: use stack trace elements to find line number
 357  9 int lineNum;
 358  9 int idxClassname = sw.indexOf(classname);
 359  0 if (idxClassname == -1) return -1;
 360   
 361  9 String theLine = sw.substring(idxClassname, sw.length());
 362   
 363  9 theLine = theLine.substring(theLine.indexOf(classname), theLine.length());
 364  9 theLine = theLine.substring(theLine.indexOf("(") + 1, theLine.length());
 365  9 theLine = theLine.substring(0, theLine.indexOf(")"));
 366   
 367  9 try {
 368  9 int i = theLine.indexOf(":") + 1;
 369  9 lineNum = Integer.parseInt(theLine.substring(i, theLine.length())) - 1;
 370    }
 371  0 catch (NumberFormatException e) { lineNum = 0; } // may be native method
 372   
 373  9 return lineNum;
 374    }
 375   
 376    /** Make a fresh JUnitTestRunner with its own class loader instance. */
 377  18 private JUnitTestRunner makeRunner() {
 378  18 ClassLoader current = JUnitTestManager.class.getClassLoader();
 379    // References to JUnit classes must match those of the current loader so that,
 380    // for example, when a test fails, the failure exception is of a class we can talk
 381    // about in the current context.
 382  18 ClassLoader parent = ShadowingClassLoader.whiteList(current, "junit", "org.junit");
 383  18 return new JUnitTestRunner(_jmc, _loaderFactory.value(parent));
 384    }
 385    }