3636
3737package edu .rice .cs .drjava .model .junit ;
3838
39+ import edu .rice .cs .drjava .ui .coverage .ReportGenerator ;
40+
3941import java .awt .EventQueue ;
4042import java .io .File ;
4143import java .io .IOException ;
4446import java .io .FileReader ;
4547import java .rmi .RemoteException ;
4648
49+ // For JaCoCo:
50+ import java .io .Writer ;
51+ import java .io .BufferedWriter ;
52+ import java .io .OutputStreamWriter ;
53+ import java .io .FileOutputStream ;
54+ import java .io .PrintWriter ;
55+ import java .io .StringWriter ;
56+ import org .jacoco .core .analysis .Analyzer ;
57+ import org .jacoco .core .analysis .CoverageBuilder ;
58+ import org .jacoco .core .analysis .IBundleCoverage ;
59+ import org .jacoco .core .tools .ExecFileLoader ;
60+ import org .jacoco .core .analysis .IClassCoverage ;
61+ import org .jacoco .core .analysis .ICounter ;
62+ import org .jacoco .core .data .ExecutionDataStore ;
63+ import org .jacoco .core .data .SessionInfoStore ;
64+ import org .jacoco .core .instr .Instrumenter ;
65+ import org .jacoco .core .runtime .IRuntime ;
66+ import org .jacoco .core .runtime .LoggerRuntime ;
67+ import org .jacoco .core .runtime .RuntimeData ;
68+ import org .jacoco .report .DirectorySourceFileLocator ;
69+ import org .jacoco .report .FileMultiReportOutput ;
70+ import org .jacoco .report .IReportVisitor ;
71+ import org .jacoco .report .html .HTMLFormatter ;
72+
4773import java .util .List ;
4874import java .util .LinkedList ;
4975import java .util .ArrayList ;
92118 */
93119public class DefaultJUnitModel implements JUnitModel , JUnitModelCallback {
94120
121+ private boolean coverage = false ;
122+
95123 /** log for use in debugging */
96124 private static Log _log = new Log ("DefaultJUnitModel.txt" , false );
97125
@@ -141,6 +169,7 @@ public DefaultJUnitModel(MainJVM jvm, CompilerModel compilerModel, GlobalModel m
141169
142170 //-------------------------- Field Setters --------------------------------//
143171
172+ public void setCoverage (boolean c ) { coverage = c ; }
144173 public void setForceTestSuffix (boolean b ) { _forceTestSuffix = b ; }
145174
146175 //------------------------ Simple Predicates ------------------------------//
@@ -312,7 +341,8 @@ private void junitOpenDefDocs(final List<OpenDefinitionsDocument> lod, final boo
312341 */
313342 private void _rawJUnitOpenDefDocs (List <OpenDefinitionsDocument > lod , final boolean allTests ) {
314343 File buildDir = _model .getBuildDirectory ();
315- // Utilities.show("Running JUnit tests. Build directory is " + buildDir);
344+
345+ //Utilities.show("Running JUnit tests. Build directory is " + buildDir);
316346
317347 /** Open java source files */
318348 HashSet <String > openDocFiles = new HashSet <String >();
@@ -328,7 +358,7 @@ private void _rawJUnitOpenDefDocs(List<OpenDefinitionsDocument> lod, final boole
328358 for (OpenDefinitionsDocument doc : lod ) /* for all nonEmpty documents in lod */ {
329359 if (doc .isSourceFile ()) { // excludes Untitled documents and open non-source files
330360 try {
331- // System.err.println("Processing " + doc);
361+ // System.err.println("Processing " + doc);
332362 File sourceRoot = doc .getSourceRoot (); // may throw an InvalidPackageException
333363
334364 // doc has valid package name; add it to list of open java source doc files
@@ -348,21 +378,21 @@ private void _rawJUnitOpenDefDocs(List<OpenDefinitionsDocument> lod, final boole
348378
349379 if (! classDirsAndRoots .containsKey (classFileDir )) {
350380 classDirsAndRoots .put (classFileDir , sourceDir );
351- // System.err.println("Adding " + classFileDir + " with source root " + sourceRoot +
352- // " to list of class directories");
381+ // System.err.println("Adding " + classFileDir + " with source root " + sourceRoot +
382+ // " to list of class directories");
353383 }
354384 }
355385 catch (InvalidPackageException e ) { /* Skip the file, since it doesn't have a valid package */ }
356386 }
357387 }
358388
359- // System.err.println("classDirs = " + classDirsAndRoots.keySet());
389+ // System.err.println("classDirs = " + classDirsAndRoots.keySet());
360390
361391 /** set of dirs potentially containing test classes */
362392 Set <File > classDirs = classDirsAndRoots .keySet ();
363393
364- // System.err.println("openDocFiles = " + openDocFiles);
365-
394+ // System.err.println("openDocFiles = " + openDocFiles);
395+
366396 /* Names of test classes. */
367397 final ArrayList <String > classNames = new ArrayList <String >();
368398
@@ -374,32 +404,31 @@ private void _rawJUnitOpenDefDocs(List<OpenDefinitionsDocument> lod, final boole
374404
375405 try {
376406 for (File dir : classDirs ) { // foreach class file directory
377- // System.err.println("Examining directory " + dir);
407+ // System.err.println("Examining directory " + dir);
378408
379409 File [] listing = dir .listFiles ();
380410
381- // System.err.println("Directory contains the files: " + Arrays.asList(listing));
411+ // System.err.println("Directory contains the files: " + Arrays.asList(listing));
382412
383413 if (listing != null ) { // listFiles may return null if there's an IO error
384414 for (File entry : listing ) { /* for each class file in the build directory */
385415
386- // System.err.println("Examining file " + entry);
416+ // System.err.println("Examining file " + entry);
387417
388418 /* ignore non-class files */
389419 String name = entry .getName ();
390420 if (! name .endsWith (".class" )) continue ;
391421
392422 /* Ignore class names that do not end in "Test" if FORCE_TEST_SUFFIX option is set */
423+ String noExtName = "" ;
393424 if (_forceTestSuffix ) {
394- String noExtName = name .substring (0 , name .length () - 6 ); // remove ".class" from name
425+ noExtName = name .substring (0 , name .length () - 6 ); // remove ".class" from name
395426 int indexOfLastDot = noExtName .lastIndexOf ('.' );
396427 String simpleClassName = noExtName .substring (indexOfLastDot + 1 );
397- // System.err.println("Simple class name is " + simpleClassName);
428+ // System.err.println("Simple class name is " + simpleClassName);
398429 if (/*isProject &&*/ ! simpleClassName .endsWith ("Test" )) continue ;
399430 }
400-
401- // System.err.println("Found test class: " + noExtName);
402-
431+
403432 /* ignore entries that do not correspond to files? Can this happen? */
404433 if (! entry .isFile ()) continue ;
405434
@@ -429,17 +458,18 @@ public void visitEnd() { }
429458
430459 /** The canonical pathname for the file (including the file name) */
431460 String javaSourceFileName = getCanonicalPath (rootDir ) + File .separator + sourceName .value ();
432- // System.err.println("Full java source fileName = " + javaSourceFileName);
461+
462+ //System.err.println("Full java source fileName = " + javaSourceFileName);
433463
434464 /* The index in fileName of the dot preceding the extension ".java", ".dj", ".dj0*, ".dj1", or ".dj2" */
435465 int indexOfExtDot = javaSourceFileName .lastIndexOf ('.' );
436- // System.err.println("indexOfExtDot = " + indexOfExtDot);
466+ // System.err.println("indexOfExtDot = " + indexOfExtDot);
437467 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));
468+ // System.err.println("File found in openDocFiles = " + openDocFiles.contains(sourceFileName));
439469
440470 /* Determine if this java source file was generated from a language levels file. */
441471 String strippedName = javaSourceFileName .substring (0 , indexOfExtDot );
442- // System.err.println("Stripped name = " + strippedName);
472+ // System.err.println("Stripped name = " + strippedName);
443473
444474 String sourceFileName ;
445475
@@ -466,10 +496,80 @@ else if (openDocFiles.contains(strippedName + OptionConstants.OLD_DJ2_FILE_EXTEN
466496 }
467497 }
468498 catch (Exception e ) {
469- // new ScrollableDialog(null, "UnexceptedExceptionThrown", e.toString(), "").show();
499+ // new ScrollableDialog(null, "UnexceptedExceptionThrown", e.toString(), "").show();
470500 throw new UnexpectedException (e ); // triggers _junitInterrupted which runs hourglassOff
471501 }
472502
503+ // JaCoCo: Create instrumented versions of class files.
504+ ReportGenerator rg = null ;
505+ RuntimeData myData = null ;
506+ ArrayList <File > myInstrumentedFiles = null ;
507+ IRuntime myRuntime = null ;
508+
509+ if (coverage ) {
510+ // Prepare to generate a JaCoCo report
511+ try {
512+ rg = new ReportGenerator (_model ,
513+ _model .getDocumentNavigator ().getSelectedDocuments (),
514+ new File ("/tmp/JaCoCo" ));
515+ } catch (Exception e ) {
516+ throw new UnexpectedException (e );
517+ }
518+
519+ myData = new RuntimeData ();
520+ myInstrumentedFiles = new ArrayList <File >();
521+ myRuntime = new LoggerRuntime ();
522+
523+ // The Instrumenter creates a modified version of our test target class
524+ // that contains additional probes for execution data recording:
525+ ArrayList <byte []> instrumenteds = new ArrayList <byte []>();
526+ for (int i = 0 ; i < files .size () ; i ++) {
527+
528+ // Instrument the i-th file
529+ try {
530+ final Instrumenter instr = new Instrumenter (myRuntime );
531+ final byte [] instrumented = instr .instrument (rg .getTargetClass (
532+ files .get (i )), classNames .get (i ));
533+ String [] pathParts = files .get (i ).getAbsolutePath ().split ("/" );
534+
535+ // Write this instrumented class to a file in /tmp/
536+ FileOutputStream fos = new FileOutputStream ("/tmp/" +
537+ pathParts [pathParts .length - 1 ].substring (0 ,
538+ pathParts [pathParts .length - 1 ].length () - 4 ) + "class" );
539+ fos .write (instrumented );
540+ fos .close ();
541+
542+ // Keep track of all of the instrumented files
543+ myInstrumentedFiles .add (new File ("/tmp/" +
544+ pathParts [pathParts .length - 1 ]));
545+ instrumenteds .add (instrumented );
546+
547+ } catch (Exception e ) {
548+ StringWriter stackTrace = new StringWriter ();
549+ e .printStackTrace (new PrintWriter (stackTrace ));
550+ Utilities .show ("INSTRUMENTATION EXCEPTION: " + stackTrace .toString ());
551+ continue ;
552+ }
553+ }
554+
555+ // Now we're ready to run our instrumented class and need to startup the
556+ // runtime first:
557+ try {
558+ myRuntime .startup (myData );
559+ } catch (Exception e ) {
560+ throw new UnexpectedException (e );
561+ }
562+
563+ _jvm .setWorkingDirectory (new File ("/tmp" ));
564+ Utilities .show ("Set working directory." );
565+ _jvm .restartInterpreterJVM (true );
566+ Utilities .show ("Restarted interpreter." );
567+ }
568+
569+ final RuntimeData data = myData ;
570+ final ArrayList <File > instrumentedFiles = myInstrumentedFiles ;
571+ final IRuntime runtime = myRuntime ;
572+
473573 /** Run the junit test suite that has already been set up on the slave JVM */
474574 _testInProgress = true ;
475575 // System.err.println("Spawning test thread");
@@ -484,33 +584,82 @@ public void run() {
484584 // _debugger.getPendingRequestManager().classPrepared(e); (which presumably
485585 // deals with preparing the class) on the event thread using invokeLater.
486586 // This, however, doesn't get executed because the event thread is still blocking --> deadlock.
487-
488587 synchronized (_compilerModel .getCompilerLock ()) {
489588 // synchronized over _compilerModel to ensure that compilation and junit testing are mutually exclusive.
490589 /** Set up junit test suite on slave JVM; get TestCase classes forming that suite */
491- List <String > tests = _jvm .findTestClasses (classNames , files ).unwrap (null );
492- // System.err.println("tests = " + tests);
590+ List <String > tests ;
591+ if (coverage ) {
592+ tests = _jvm .findTestClasses (classNames , instrumentedFiles ).unwrap (null );
593+ } else {
594+ tests = _jvm .findTestClasses (classNames , files ).unwrap (null );
595+ }
596+
493597 if (tests == null || tests .isEmpty ()) {
494598 nonTestCase (allTests , false );
495599 return ;
496600 }
497601 }
498602
499603 try {
500- // Utilities.show("Starting JUnit");
501-
502604 _notifyJUnitStarted ();
503- boolean testsPresent = _jvm .runTestSuite (); // The false return value could be changed to an exception.
504- if (! testsPresent ) throw new RemoteException ("No unit test classes were passed to the slave JVM" );
605+ // The false return value could be changed to an exception.
606+ boolean testsPresent = _jvm .runTestSuite ();
607+ if (! testsPresent ) {
608+ throw new RemoteException ("No unit test classes were passed to the slave JVM" );
609+ }
505610 }
506- catch (RemoteException e ) { // Unit testing aborted; cleanup; hourglassOff already called in junitStarted
611+ catch (RemoteException e ) {
612+ // Unit testing aborted; cleanup; hourglassOff already called in junitStarted
507613 _notifyJUnitEnded (); // balances junitStarted()
508614 _testInProgress = false ;
509615 }
510616 }
617+
511618 }).start ();
512- }
513-
619+
620+ // At the end of test execution we collect execution data and shutdown
621+ // the runtime:
622+ //highlight(rg, true);
623+ if (coverage ) {
624+ Utilities .show ("Collecting session info..." );
625+ final ExecutionDataStore executionData = new ExecutionDataStore ();
626+ final SessionInfoStore sessionInfos = new SessionInfoStore ();
627+ myData .collect (executionData , sessionInfos , false );
628+ runtime .shutdown ();
629+
630+ // Together with the original class definition we can calculate coverage
631+ // information:
632+ final CoverageBuilder coverageBuilder = new CoverageBuilder ();
633+ final Analyzer analyzer = new Analyzer (executionData , coverageBuilder );
634+
635+ try {
636+ for (int i = 0 ; i < classNames .size (); i ++) {
637+ Utilities .show ("className = " + classNames .get (i ));
638+ analyzer .analyzeClass (rg .getTargetClass (files .get (i )),
639+ classNames .get (i ));
640+ }
641+
642+ //printCoverage(coverageBuilder);
643+
644+ // Run the structure analyzer on a single class folder to build up
645+ // the coverage model. The process would be similar if your classes
646+ // were in a jar file. Typically you would create a bundle for each
647+ // class folder and each jar you want in your report. If you have
648+ // more than one bundle you will need to add a grouping node to your
649+ // report
650+ Utilities .show ("rg.getSourceDirectory().getName() = " + rg .getSourceDirectory ().getName ());
651+ final IBundleCoverage bundleCoverage = coverageBuilder .getBundle (
652+ rg .getSourceDirectory ().getName ());
653+ rg .createReport (bundleCoverage , executionData , sessionInfos );
654+ } catch (Exception e ) {
655+ StringWriter stackTrace = new StringWriter ();
656+ e .printStackTrace (new PrintWriter (stackTrace ));
657+ Utilities .show ("ANALYSIS EXCEPTION: " + stackTrace .toString ());
658+ }
659+
660+ }
661+ }
662+
514663//-------------------------------- Helpers --------------------------------//
515664
516665 /** Helper method to notify JUnitModel listeners that JUnit test suite execution has started. */
0 commit comments