Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 653   Methods: 54
NCLOC: 319   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
DefaultJUnitModel.java 60% 75% 74.1% 72.2%
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 java.awt.EventQueue;
 40    import java.io.File;
 41    import java.io.IOException;
 42    import java.io.BufferedReader;
 43    import java.io.InputStreamReader;
 44    import java.io.FileReader;
 45    import java.rmi.RemoteException;
 46   
 47    import java.util.List;
 48    import java.util.LinkedList;
 49    import java.util.ArrayList;
 50    import java.util.Arrays;
 51    import java.util.HashMap;
 52    import java.util.HashSet;
 53    import java.util.Set;
 54    import java.util.TreeMap;
 55    import java.util.jar.JarFile;
 56    import java.util.jar.JarEntry;
 57   
 58    import javax.swing.JOptionPane;
 59   
 60    import edu.rice.cs.drjava.config.BooleanOption;
 61    import edu.rice.cs.drjava.model.GlobalModel;
 62    import edu.rice.cs.drjava.model.FileMovedException;
 63    import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
 64    import edu.rice.cs.drjava.model.DrJavaFileUtils;
 65    import edu.rice.cs.drjava.model.repl.newjvm.MainJVM;
 66    import edu.rice.cs.drjava.model.compiler.CompilerModel;
 67    import edu.rice.cs.drjava.model.compiler.CompilerListener;
 68    import edu.rice.cs.drjava.model.compiler.DummyCompilerListener;
 69    import edu.rice.cs.drjava.model.definitions.InvalidPackageException;
 70    import edu.rice.cs.drjava.ui.DrJavaErrorHandler;
 71    import edu.rice.cs.drjava.config.OptionConstants;
 72   
 73    import edu.rice.cs.plt.io.IOUtil;
 74    import edu.rice.cs.plt.iter.IterUtil;
 75    import edu.rice.cs.plt.lambda.Box;
 76    import edu.rice.cs.plt.lambda.SimpleBox;
 77    import edu.rice.cs.plt.concurrent.JVMBuilder;
 78    import edu.rice.cs.util.FileOps;
 79    import edu.rice.cs.util.UnexpectedException;
 80    import edu.rice.cs.util.classloader.ClassFileError;
 81    import edu.rice.cs.util.text.SwingDocument;
 82    import edu.rice.cs.util.swing.Utilities;
 83    import edu.rice.cs.util.Log;
 84   
 85    import org.objectweb.asm.*;
 86   
 87    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 88    import edu.rice.cs.drjava.model.compiler.LanguageLevelStackTraceMapper;
 89   
 90    /** Manages unit testing via JUnit.
 91    * @version $Id: DefaultJUnitModel.java 5456 2011-11-18 07:01:53Z rcartwright $
 92    */
 93    public class DefaultJUnitModel implements JUnitModel, JUnitModelCallback {
 94   
 95    /** log for use in debugging */
 96    private static Log _log = new Log("DefaultJUnitModel.txt", false);
 97   
 98    /** Manages listeners to this model. */
 99    private final JUnitEventNotifier _notifier = new JUnitEventNotifier();
 100   
 101    /** RMI interface to a secondary JVM for running tests. Using a second JVM prevents interactions and tests from
 102    * corrupting the state of DrJava.
 103    */
 104    private final MainJVM _jvm;
 105   
 106    /** The compiler model. It contains a lock used to prevent simultaneous test and compile. It also tracks the number
 107    * errors in the last compilation, which is required information if junit forces compilation.
 108    */
 109    private final CompilerModel _compilerModel;
 110   
 111    /** The global model to which the JUnitModel belongs */
 112    private final GlobalModel _model;
 113   
 114    /** The error model containing all current JUnit errors. */
 115    private volatile JUnitErrorModel _junitErrorModel;
 116   
 117    /** State flag to prevent starting new tests on top of old ones and to prevent resetting interactions after compilation
 118    * is forced by unit testing. This field is NOT REDUNDANT, it is used in junitJVMReady.
 119    */
 120    private volatile boolean _testInProgress = false;
 121   
 122    /** State flag to record if test classes in projects must end in "Test" */
 123    private boolean _forceTestSuffix = false;
 124   
 125    /** The document used to display JUnit test results. Used only for testing. */
 126    private final SwingDocument _junitDoc = new SwingDocument();
 127   
 128    /** Main constructor.
 129    * @param jvm RMI interface to a secondary JVM for running tests
 130    * @param compilerModel the CompilerModel, used only as a lock to prevent simultaneous test and compile
 131    * @param model used only for getSourceFile
 132    */
 133  157 public DefaultJUnitModel(MainJVM jvm, CompilerModel compilerModel, GlobalModel model) {
 134  157 _jvm = jvm;
 135  157 _compilerModel = compilerModel;
 136  157 _model = model;
 137  157 _junitErrorModel = new JUnitErrorModel(new JUnitError[0], _model, false);
 138  157 BooleanOption suffixOption = OptionConstants.FORCE_TEST_SUFFIX;
 139  157 _forceTestSuffix = edu.rice.cs.drjava.DrJava.getConfig().getSetting(suffixOption).booleanValue();
 140    }
 141   
 142    //-------------------------- Field Setters --------------------------------//
 143   
 144  0 public void setForceTestSuffix(boolean b) { _forceTestSuffix = b; }
 145   
 146    //------------------------ Simple Predicates ------------------------------//
 147   
 148  47 public boolean isTestInProgress() { return _testInProgress; }
 149   
 150    //------------------------Listener Management -----------------------------//
 151   
 152    /** Add a JUnitListener to the model.
 153    * @param listener a listener that reacts to JUnit events
 154    */
 155  157 public void addListener(JUnitListener listener) { _notifier.addListener(listener); }
 156   
 157    /** Remove a JUnitListener from the model. If the listener is not currently listening to this model, this method
 158    * has no effect.
 159    * @param listener a listener that reacts to JUnit events
 160    */
 161  0 public void removeListener(JUnitListener listener) { _notifier.removeListener(listener); }
 162   
 163    /** Removes all JUnitListeners from this model. */
 164  0 public void removeAllListeners() { _notifier.removeAllListeners(); }
 165   
 166    //-------------------------------- Triggers --------------------------------//
 167   
 168    /** Used only for testing. */
 169  3 public SwingDocument getJUnitDocument() { return _junitDoc; }
 170   
 171    /** Creates a JUnit test suite over all currently open documents and runs it. If the class file
 172    * associated with a file is not a test case, it is ignored.
 173    */
 174  2 public void junitAll() { junitDocs(_model.getOpenDefinitionsDocuments()); }
 175   
 176    /** Creates a JUnit test suite over all currently open documents and runs it. If a class file associated with a
 177    * source file is not a test case, it will be ignored. Synchronized against the compiler model to prevent
 178    * testing and compiling at the same time, which would create invalid results.
 179    */
 180  0 public void junitProject() {
 181  0 LinkedList<OpenDefinitionsDocument> lod = new LinkedList<OpenDefinitionsDocument>();
 182   
 183  0 for (OpenDefinitionsDocument doc : _model.getOpenDefinitionsDocuments()) {
 184  0 if (doc.inProjectPath()) lod.add(doc);
 185    }
 186  0 junitOpenDefDocs(lod, true);
 187    }
 188   
 189    // /** Forwards the classnames and files to the test manager to test all of them; does not notify
 190    // * since we don't have ODD's to send out with the notification of junit start.
 191    // * @param qualifiedClassnames a list of all the qualified class names to test.
 192    // * @param files a list of their source files in the same order as qualified class names.
 193    // */
 194    // public void junitClasses(List<String> qualifiedClassnames, List<File> files) {
 195    // Utilities.showDebug("junitClasses(" + qualifiedClassnames + ", " + files);
 196    // synchronized(_compilerModel.getCompilerLock()) {
 197    //
 198    // // Check _testInProgress
 199    // if (_testInProgress) return;
 200    //
 201    // List<String> testClasses;
 202    // try { testClasses = _jvm.findTestClasses(qualifiedClassnames, files); }
 203    // catch(IOException e) { throw new UnexpectedException(e); }
 204    //
 205    //// System.err.println("Found test classes: " + testClasses);
 206    //
 207    // if (testClasses.isEmpty()) {
 208    // nonTestCase(true);
 209    // return;
 210    // }
 211    // _notifier.junitClassesStarted();
 212    // _testInProgress = true;
 213    // try { _jvm.runTestSuite(); }
 214    // catch(Exception e) {
 215    //// System.err.println("Threw exception " + e);
 216    // _notifier.junitEnded();
 217    // _testInProgress = false;
 218    // throw new UnexpectedException(e);
 219    // }
 220    // }
 221    // }
 222   
 223  2 public void junitDocs(List<OpenDefinitionsDocument> lod) { junitOpenDefDocs(lod, true); }
 224   
 225    /** Runs JUnit on the current document. Forces the user to compile all open documents before proceeding. */
 226  17 public void junit(OpenDefinitionsDocument doc) throws ClassNotFoundException, IOException {
 227  17 debug.logStart("junit(doc)");
 228    // new ScrollableDialog(null, "junit(" + doc + ") called in DefaultJunitModel", "", "").show();
 229  17 File testFile;
 230  17 try {
 231  17 testFile = doc.getFile();
 232  17 if (testFile == null) { // document is untitiled: abort unit testing and return
 233  0 nonTestCase(false, false);
 234  0 debug.logEnd("junit(doc): no corresponding file");
 235  0 return;
 236    }
 237    }
 238    catch(FileMovedException fme) { /* do nothing */ }
 239   
 240  17 LinkedList<OpenDefinitionsDocument> lod = new LinkedList<OpenDefinitionsDocument>();
 241  17 lod.add(doc);
 242  17 junitOpenDefDocs(lod, false);
 243  17 debug.logEnd("junit(doc)");
 244    }
 245   
 246    /** Ensures that all documents have been compiled since their last modification and then delegates the actual testing
 247    * to _rawJUnitOpenTestDocs. */
 248  19 private void junitOpenDefDocs(final List<OpenDefinitionsDocument> lod, final boolean allTests) {
 249    // If a test is running, don't start another one.
 250   
 251    // System.err.println("junitOpenDefDocs(" + lod + ", " + allTests + ", " + _testInProgress + ")");
 252   
 253    // Check_testInProgress flag
 254  0 if (_testInProgress) return;
 255   
 256    // Reset the JUnitErrorModel, fixes bug #907211 "Test Failures Not Cleared Properly".
 257  19 _junitErrorModel = new JUnitErrorModel(new JUnitError[0], null, false);
 258   
 259    // System.err.println("Retrieved JUnit error model");
 260  19 final List<OpenDefinitionsDocument> outOfSync = _model.getOutOfSyncDocuments(lod);
 261  19 if ((outOfSync.size() > 0) || _model.hasModifiedDocuments(lod)) {
 262    /* hasOutOfSyncDocments(lod) can return false when some documents have not been successfully compiled; the
 263    * granularity of time-stamping and the presence of multiple classes in a file (some of which compile
 264    * successfully) can produce false reports. */
 265    // System.err.println("Out of sync documents exist");
 266  3 CompilerListener testAfterCompile = new DummyCompilerListener() {
 267  0 @Override public void compileAborted(Exception e) {
 268    // gets called if there are modified files and the user chooses NOT to save the files
 269    // see bug report 2582488: Hangs If Testing Modified File, But Choose "No" for Saving
 270  0 final CompilerListener listenerThis = this;
 271  0 try {
 272  0 nonTestCase(allTests, false);
 273    }
 274    finally { // always remove this listener after its first execution
 275  0 EventQueue.invokeLater(new Runnable() {
 276  0 public void run() { _compilerModel.removeListener(listenerThis); }
 277    });
 278    }
 279    }
 280   
 281  3 @Override public void compileEnded(File workDir, List<? extends File> excludedFiles) {
 282  3 final CompilerListener listenerThis = this;
 283  3 try {
 284  3 if (_model.hasOutOfSyncDocuments(lod) || _model.getNumCompErrors() > 0) {
 285  1 nonTestCase(allTests, _model.getNumCompErrors() > 0);
 286  1 return;
 287    }
 288  2 EventQueue.invokeLater(new Runnable() { // defer running this code; would prefer to waitForInterpreter
 289  2 public void run() { _rawJUnitOpenDefDocs(lod, allTests); }
 290    });
 291    }
 292    finally { // always remove this listener after its first execution
 293  3 EventQueue.invokeLater(new Runnable() {
 294  3 public void run() { _compilerModel.removeListener(listenerThis); }
 295    });
 296    }
 297    }
 298    };
 299   
 300    // System.err.println("Notifying JUnitModelListener");
 301  3 _testInProgress = true;
 302  3 _notifyCompileBeforeJUnit(testAfterCompile, outOfSync);
 303  3 _testInProgress = false;
 304    }
 305   
 306  16 else _rawJUnitOpenDefDocs(lod, allTests);
 307    }
 308   
 309    /** Runs all TestCases in the document list lod; assumes all documents have been compiled. It finds the TestCase
 310    * classes by searching the build directories for the documents. Note: caller must respond to thrown exceptions
 311    * by invoking _junitUnitInterrupted (to run hourglassOff() and reset the unit testing UI).
 312    */
 313  18 private void _rawJUnitOpenDefDocs(List<OpenDefinitionsDocument> lod, final boolean allTests) {
 314  18 File buildDir = _model.getBuildDirectory();
 315    // Utilities.show("Running JUnit tests. Build directory is " + buildDir);
 316   
 317    /** Open java source files */
 318  18 HashSet<String> openDocFiles = new HashSet<String>();
 319   
 320    /** A map whose keys are directories containing class files corresponding to open java source files.
 321    * Their values are the corresponding source roots.
 322    */
 323  18 HashMap<File, File> classDirsAndRoots = new HashMap<File, File>();
 324   
 325    // Initialize openDocFiles and classDirsAndRoots
 326    // All packageNames should be valid because all source files are compiled
 327   
 328  18 for (OpenDefinitionsDocument doc: lod) /* for all nonEmpty documents in lod */ {
 329  20 if (doc.isSourceFile()) { // excludes Untitled documents and open non-source files
 330  18 try {
 331    // System.err.println("Processing " + doc);
 332  18 File sourceRoot = doc.getSourceRoot(); // may throw an InvalidPackageException
 333   
 334    // doc has valid package name; add it to list of open java source doc files
 335  18 openDocFiles.add(doc.getCanonicalPath());
 336   
 337  18 String packagePath = doc.getPackageName().replace('.', File.separatorChar);
 338   
 339    // Add (canonical path name for) build directory for doc to classDirs
 340   
 341  18 File buildRoot = (buildDir == FileOps.NULL_FILE) ? sourceRoot: buildDir;
 342   
 343  18 File classFileDir = new File(IOUtil.attemptCanonicalFile(buildRoot), packagePath);
 344   
 345  18 File sourceDir =
 346  18 (buildDir == FileOps.NULL_FILE) ? classFileDir :
 347    new File(IOUtil.attemptCanonicalFile(sourceRoot), packagePath);
 348   
 349  18 if (! classDirsAndRoots.containsKey(classFileDir)) {
 350  18 classDirsAndRoots.put(classFileDir, sourceDir);
 351    // System.err.println("Adding " + classFileDir + " with source root " + sourceRoot +
 352    // " to list of class directories");
 353    }
 354    }
 355    catch (InvalidPackageException e) { /* Skip the file, since it doesn't have a valid package */ }
 356    }
 357    }
 358   
 359    // System.err.println("classDirs = " + classDirsAndRoots.keySet());
 360   
 361    /** set of dirs potentially containing test classes */
 362  18 Set<File> classDirs = classDirsAndRoots.keySet();
 363   
 364    // System.err.println("openDocFiles = " + openDocFiles);
 365   
 366    /* Names of test classes. */
 367  18 final ArrayList<String> classNames = new ArrayList<String>();
 368   
 369    /* Source files corresonding to potential test class files */
 370  18 final ArrayList<File> files = new ArrayList<File>();
 371   
 372    /* Flag indicating if project is open */
 373  18 boolean isProject = _model.isProjectActive();
 374   
 375  18 try {
 376  18 for (File dir: classDirs) { // foreach class file directory
 377    // System.err.println("Examining directory " + dir);
 378   
 379  18 File[] listing = dir.listFiles();
 380   
 381    // System.err.println("Directory contains the files: " + Arrays.asList(listing));
 382   
 383  18 if (listing != null) { // listFiles may return null if there's an IO error
 384  18 for (File entry : listing) { /* for each class file in the build directory */
 385   
 386    // System.err.println("Examining file " + entry);
 387   
 388    /* ignore non-class files */
 389  46 String name = entry.getName();
 390  23 if (! name.endsWith(".class")) continue;
 391   
 392    /* Ignore class names that do not end in "Test" if FORCE_TEST_SUFFIX option is set */
 393  23 if (_forceTestSuffix) {
 394  0 String noExtName = name.substring(0, name.length() - 6); // remove ".class" from name
 395  0 int indexOfLastDot = noExtName.lastIndexOf('.');
 396  0 String simpleClassName = noExtName.substring(indexOfLastDot + 1);
 397    // System.err.println("Simple class name is " + simpleClassName);
 398  0 if (/*isProject &&*/ ! simpleClassName.endsWith("Test")) continue;
 399    }
 400   
 401    // System.err.println("Found test class: " + noExtName);
 402   
 403    /* ignore entries that do not correspond to files? Can this happen? */
 404  0 if (! entry.isFile()) continue;
 405   
 406    // Add this class and the corrresponding source file to classNames and files, respectively.
 407    // Finding the source file is non-trivial because it may be a language-levels file
 408   
 409  23 try {
 410  23 final Box<String> className = new SimpleBox<String>();
 411  23 final Box<String> sourceName = new SimpleBox<String>();
 412  23 new ClassReader(IOUtil.toByteArray(entry)).accept(new ClassVisitor() {
 413  23 public void visit(int version, int access, String name, String sig, String sup, String[] inters) {
 414  23 className.set(name.replace('/', '.'));
 415    }
 416  23 public void visitSource(String source, String debug) {
 417  23 sourceName.set(source);
 418    }
 419  0 public void visitOuterClass(String owner, String name, String desc) { }
 420  0 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return null; }
 421  0 public void visitAttribute(Attribute attr) { }
 422  0 public void visitInnerClass(String name, String out, String in, int access) { }
 423  0 public FieldVisitor visitField(int a, String n, String d, String s, Object v) { return null; }
 424  67 public MethodVisitor visitMethod(int a, String n, String d, String s, String[] e) { return null; }
 425  23 public void visitEnd() { }
 426    }, 0);
 427   
 428  23 File rootDir = classDirsAndRoots.get(dir);
 429   
 430    /** The canonical pathname for the file (including the file name) */
 431  23 String javaSourceFileName = getCanonicalPath(rootDir) + File.separator + sourceName.value();
 432    // System.err.println("Full java source fileName = " + javaSourceFileName);
 433   
 434    /* The index in fileName of the dot preceding the extension ".java", ".dj", ".dj0*, ".dj1", or ".dj2" */
 435  23 int indexOfExtDot = javaSourceFileName.lastIndexOf('.');
 436    // System.err.println("indexOfExtDot = " + indexOfExtDot);
 437  0 if (indexOfExtDot == -1) continue; // RMI stub class files return source file names without extensions
 438    // System.err.println("File found in openDocFiles = " + openDocFiles.contains(sourceFileName));
 439   
 440    /* Determine if this java source file was generated from a language levels file. */
 441  23 String strippedName = javaSourceFileName.substring(0, indexOfExtDot);
 442    // System.err.println("Stripped name = " + strippedName);
 443   
 444  23 String sourceFileName;
 445   
 446  20 if (openDocFiles.contains(javaSourceFileName)) sourceFileName = javaSourceFileName;
 447  3 else if (openDocFiles.contains(strippedName + OptionConstants.DJ_FILE_EXTENSION))
 448  0 sourceFileName = strippedName + OptionConstants.DJ_FILE_EXTENSION;
 449  3 else if (openDocFiles.contains(strippedName + OptionConstants.OLD_DJ0_FILE_EXTENSION))
 450  0 sourceFileName = strippedName + OptionConstants.OLD_DJ0_FILE_EXTENSION;
 451  3 else if (openDocFiles.contains(strippedName + OptionConstants.OLD_DJ1_FILE_EXTENSION))
 452  0 sourceFileName = strippedName + OptionConstants.OLD_DJ1_FILE_EXTENSION;
 453  3 else if (openDocFiles.contains(strippedName + OptionConstants.OLD_DJ2_FILE_EXTENSION))
 454  0 sourceFileName = strippedName + OptionConstants.OLD_DJ2_FILE_EXTENSION;
 455  3 else continue; // no matching source file is open
 456   
 457  20 File sourceFile = new File(sourceFileName);
 458  20 classNames.add(className.value());
 459  20 files.add(sourceFile);
 460    // System.err.println("Class " + className + "added to classNames. File " + sourceFileName +
 461    // " added to files.");
 462    }
 463    catch(IOException e) { /* ignore it; can't read class file */ }
 464    }
 465    }
 466    }
 467    }
 468    catch(Exception e) {
 469    // new ScrollableDialog(null, "UnexceptedExceptionThrown", e.toString(), "").show();
 470  0 throw new UnexpectedException(e); // triggers _junitInterrupted which runs hourglassOff
 471    }
 472   
 473    /** Run the junit test suite that has already been set up on the slave JVM */
 474  18 _testInProgress = true;
 475    // System.err.println("Spawning test thread");
 476  18 new Thread(new Runnable() { // this thread is not joined, but the wait/notify scheme guarantees that it ends
 477  18 public void run() {
 478    // TODO: should we disable compile commands while testing? Should we use protected flag instead of lock?
 479    // Utilities.show("Preparing to synchronize");
 480   
 481    // The call to findTestClasses had to be moved out of the event thread (bug 2722310)
 482    // The event thread is still blocked in findTestClasses when JUnit needs to
 483    // have a class prepared. This invokes EventHandlerThread._handleClassPrepareEvent, which puts a call to
 484    // _debugger.getPendingRequestManager().classPrepared(e); (which presumably
 485    // deals with preparing the class) on the event thread using invokeLater.
 486    // This, however, doesn't get executed because the event thread is still blocking --> deadlock.
 487   
 488  18 synchronized(_compilerModel.getCompilerLock()) {
 489    // synchronized over _compilerModel to ensure that compilation and junit testing are mutually exclusive.
 490    /** Set up junit test suite on slave JVM; get TestCase classes forming that suite */
 491  18 List<String> tests = _jvm.findTestClasses(classNames, files).unwrap(null);
 492    // System.err.println("tests = " + tests);
 493  18 if (tests == null || tests.isEmpty()) {
 494  2 nonTestCase(allTests, false);
 495  2 return;
 496    }
 497    }
 498   
 499  16 try {
 500    // Utilities.show("Starting JUnit");
 501   
 502  16 _notifyJUnitStarted();
 503  16 boolean testsPresent = _jvm.runTestSuite(); // The false return value could be changed to an exception.
 504  1 if (! testsPresent) throw new RemoteException("No unit test classes were passed to the slave JVM");
 505    }
 506    catch(RemoteException e) { // Unit testing aborted; cleanup; hourglassOff already called in junitStarted
 507  1 _notifyJUnitEnded(); // balances junitStarted()
 508  1 _testInProgress = false;
 509    }
 510    }
 511    }).start();
 512    }
 513   
 514    //-------------------------------- Helpers --------------------------------//
 515   
 516    /** Helper method to notify JUnitModel listeners that JUnit test suite execution has started. */
 517  16 private void _notifyJUnitStarted() {
 518    // Use EventQueue.invokeLater so that notification is deferred when running in the event thread.
 519  16 EventQueue.invokeLater(new Runnable() { public void run() { _notifier.junitStarted(); } });
 520    }
 521   
 522    /** Helper method to notify JUnitModel listeners that JUnit test suite execution has just ended. */
 523  16 private void _notifyJUnitEnded() {
 524    // Use EventQueue.invokeLater so that notification is deferred when running in the event thread.
 525  16 EventQueue.invokeLater(new Runnable() { public void run() { _notifier.junitEnded(); } });
 526    }
 527   
 528    /** Helper method to notify JUnitModel listeners that all open files must be compiled before JUnit is run. */
 529  3 private void _notifyCompileBeforeJUnit(final CompilerListener testAfterCompile,
 530    final List<OpenDefinitionsDocument> outOfSync) {
 531  3 Utilities.invokeLater(new Runnable() {
 532  3 public void run() { _notifier.compileBeforeJUnit(testAfterCompile, outOfSync); }
 533    });
 534    }
 535   
 536    /** Helper method to notify JUnitModel listeners that JUnit aborted before any tests could be run. */
 537  3 private void _notifyNonTestCase(final boolean testAll, final boolean didCompileFail) {
 538  3 Utilities.invokeLater(new Runnable() { public void run() { _notifier.nonTestCase(testAll, didCompileFail); } });
 539    }
 540   
 541  23 private String getCanonicalPath(File f) throws IOException {
 542  0 if (f == null) return "";
 543  23 return f.getCanonicalPath();
 544    }
 545   
 546    //----------------------------- Error Results -----------------------------//
 547   
 548    /** Gets the JUnitErrorModel, which contains error info for the last test run. */
 549  55 public JUnitErrorModel getJUnitErrorModel() { return _junitErrorModel; }
 550   
 551    /** Resets the junit error state to have no errors. */
 552  4 public void resetJUnitErrors() { _junitErrorModel = new JUnitErrorModel(new JUnitError[0], _model, false); }
 553   
 554    //---------------------------- Model Callbacks ----------------------------//
 555   
 556    /** Called from the JUnitTestManager if its given className is not a test case.
 557    * @param isTestAll whether or not it was a use of the test all button
 558    * @param didCompileFail whether or not a compile before this JUnit attempt failed
 559    */
 560  3 public void nonTestCase(final boolean isTestAll, boolean didCompileFail) {
 561    // NOTE: junitStarted is called in a different thread from the testing thread. The _testInProgress flag
 562    // is used to prevent a new test from being started and overrunning the existing one.
 563    // Utilities.show("DefaultJUnitModel.nonTestCase(" + isTestAll + ") called");
 564  3 _notifyNonTestCase(isTestAll, didCompileFail);
 565  3 _testInProgress = false;
 566    }
 567   
 568    /** Called to indicate that an illegal class file was encountered
 569    * @param e the ClassFileObject describing the error.
 570    */
 571  0 public void classFileError(final ClassFileError e) {
 572  0 Utilities.invokeLater(new Runnable() { public void run() {_notifier.classFileError(e); } });
 573    }
 574   
 575    /** Called to indicate that a suite of tests has started running.
 576    * @param numTests The number of tests in the suite to be run.
 577    */
 578  16 public void testSuiteStarted(final int numTests) {
 579  16 Utilities.invokeLater(new Runnable() { public void run() { _notifier.junitSuiteStarted(numTests); } });
 580    }
 581   
 582    /** Called when a particular test is started.
 583    * @param testName The name of the test being started.
 584    */
 585  25 public void testStarted(final String testName) {
 586  25 Utilities.invokeLater(new Runnable() { public void run() { _notifier.junitTestStarted(testName); } });
 587    }
 588   
 589    /** Called when a particular test has ended.
 590    * @param testName The name of the test that has ended.
 591    * @param wasSuccessful Whether the test passed or not.
 592    * @param causedError If not successful, whether the test caused an error or simply failed.
 593    */
 594  24 public void testEnded(final String testName, final boolean wasSuccessful, final boolean causedError) {
 595  24 EventQueue.invokeLater(new Runnable() {
 596  24 public void run() { _notifier.junitTestEnded(testName, wasSuccessful, causedError); }
 597    });
 598    }
 599   
 600    /** Called when a full suite of tests has finished running. Does not necessarily run in event thread.
 601    * @param errors The array of errors from all failed tests in the suite.
 602    */
 603  15 public void testSuiteEnded(final JUnitError[] errors) {
 604    // new ScrollableDialog(null, "DefaultJUnitModel.testSuiteEnded(...) called", "", "").show();
 605  15 Utilities.invokeLater(new Runnable() { public void run() {
 606  15 List<File> files = new ArrayList<File>();
 607  0 for(OpenDefinitionsDocument odd: _model.getLLOpenDefinitionsDocuments()) { files.add(odd.getRawFile()); }
 608    // Utilities.show("errors.length = " + errors.length + " files = " + files);
 609  15 for(JUnitError e: errors){
 610  13 try {
 611  13 e.setStackTrace(_compilerModel.getLLSTM().replaceStackTrace(e.stackTrace(),files));
 612  0 } catch(Exception ex) { DrJavaErrorHandler.record(ex); }
 613  13 File f = e.file();
 614  13 if ((f != null) && (DrJavaFileUtils.isLLFile(f))) {
 615  0 String dn = DrJavaFileUtils.getJavaForLLFile(f.getName());
 616  0 StackTraceElement ste = new StackTraceElement(e.className(), "", dn, e.lineNumber());
 617  0 ste = _compilerModel.getLLSTM().replaceStackTraceElement(ste, f);
 618  0 e.setLineNumber(ste.getLineNumber());
 619    }
 620    }
 621  15 _junitErrorModel = new JUnitErrorModel(errors, _model, true);
 622  15 _notifyJUnitEnded();
 623  15 _testInProgress = false;
 624    // new ScrollableDialog(null, "DefaultJUnitModel.testSuiteEnded(...) finished", "", "").show();
 625    }});
 626    }
 627   
 628   
 629    /** Called when the JUnitTestManager wants to open a file that is not currently open.
 630    * @param className the name of the class for which we want to find the file
 631    * @return the file associated with the given class
 632    */
 633  4 public File getFileForClassName(String className) {
 634    // TODO: What about language level file extensions? What about Habanero Java extension?
 635  4 return _model.getSourceFile(className + OptionConstants.JAVA_FILE_EXTENSION);
 636    }
 637   
 638    /** Returns the current classpath in use by the JUnit JVM. */
 639  0 public Iterable<File> getClassPath() { return _jvm.getClassPath().unwrap(IterUtil.<File>empty()); }
 640   
 641    /** Called when the JVM used for unit tests has registered. Does not necessarily run in even thread. */
 642  177 public void junitJVMReady() {
 643  177 Utilities.invokeLater(new Runnable() { public void run() {
 644  177 if (! _testInProgress) return;
 645   
 646  0 JUnitError[] errors = new JUnitError[1];
 647  0 errors[0] = new JUnitError("Previous test suite was interrupted", true, "");
 648  0 _junitErrorModel = new JUnitErrorModel(errors, _model, true);
 649  0 _notifyJUnitEnded();
 650  0 _testInProgress = false;
 651    }});
 652    }
 653    }