Skip to content

Commit 3d8b3a6

Browse files
committed
This revision is an attempt to make code coverage work inside DrJava; it does not quite work. It includes extensive logging of class loader operations in the context of class loading. The following classes were modified:
modified: lib/plt.jar modified: src/edu/rice/cs/drjava/model/coverage/CoverageMetadata.java modified: src/edu/rice/cs/drjava/model/coverage/MemoryClassLoader.java modified: src/edu/rice/cs/drjava/model/junit/DefaultJUnitModel.java modified: src/edu/rice/cs/drjava/model/junit/JUnitTestManager.java modified: src/edu/rice/cs/drjava/model/junit/JUnitTestRunner.java modified: src/edu/rice/cs/drjava/model/repl/newjvm/ClassPathManager.java modified: src/edu/rice/cs/drjava/ui/MainFrame.java modified: ../platform/build.xml modified: ../plt/build.xml modified: ../plt/src/edu/rice/cs/plt/reflect/PathClassLoader.java modified: ../plt/src/edu/rice/cs/plt/reflect/PreemptingClassLoader.java modified: ../plt/src/edu/rice/cs/plt/reflect/ShadowingClassLoader.java
1 parent 96d25da commit 3d8b3a6

File tree

13 files changed

+259
-216
lines changed

13 files changed

+259
-216
lines changed

drjava/lib/plt.jar

1.36 KB
Binary file not shown.

drjava/src/edu/rice/cs/drjava/model/coverage/CoverageMetadata.java

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,16 @@
3939
import java.io.Serializable;
4040

4141
public class CoverageMetadata implements Serializable {
42-
43-
private boolean doCoverage;
44-
private String outputDirectory;
45-
46-
public CoverageMetadata(boolean doCoverage, String outputDirectory) {
47-
this.doCoverage = doCoverage;
48-
this.outputDirectory = outputDirectory;
49-
}
50-
51-
public boolean getFlag() {
52-
return this.doCoverage;
53-
}
54-
55-
public String getOutdirPath() {
56-
return this.outputDirectory;
57-
}
42+
43+
private boolean doCoverage;
44+
private String outputDirectory;
45+
46+
public CoverageMetadata(boolean doCoverage, String outputDirectory) {
47+
this.doCoverage = doCoverage;
48+
this.outputDirectory = outputDirectory;
49+
}
50+
51+
public boolean getFlag() { return this.doCoverage; }
52+
53+
public String getOutdirPath() { return this.outputDirectory; }
5854
}

drjava/src/edu/rice/cs/drjava/model/coverage/MemoryClassLoader.java

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,47 @@
66

77
import edu.rice.cs.util.Log;
88

9-
/** A class loader that loads classes from in-memory data. */
9+
/** A class loader that loads classes from in-memory data. The parent class loader must be a ShadowingClassLoader
10+
* that fails to load classes for the class names in memory. */
1011
public class MemoryClassLoader extends ClassLoader {
1112

1213
private static final Log _log = new Log("JUnitTestManager.txt", true);
1314

14-
public MemoryClassLoader(ClassLoader parent) { super(parent); }
15-
16-
private final Map<String, byte[]> definitions =
17-
new HashMap<String, byte[]>();
18-
19-
/** Add a in-memory representation of a class.
20-
* @param name name of the class
21-
* @param bytes class definition
22-
*/
23-
public void addDefinition(final String name, final byte[] bytes) {
24-
definitions.put(name, bytes);
25-
}
26-
27-
@Override
28-
protected Class<?> loadClass(final String name, final boolean resolve)
29-
throws ClassNotFoundException {
30-
final byte[] bytes = definitions.get(name);
31-
if (bytes != null) {
32-
return defineClass(name, bytes, 0, bytes.length);
33-
}
34-
_log.log("Calling loadClass(" + name + ", " + resolve + ")");
35-
return super.loadClass(name, resolve);
15+
private ClassLoader _parent;
16+
public MemoryClassLoader(ClassLoader parent) {
17+
super(parent);
18+
_parent = parent;
19+
_log.log("Creating MemoryClassLoader with parent = " + parent);
20+
}
21+
22+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
23+
_log.log("MemoryClassLoader.loadClass(" + name + ", " + resolve + ") called");
24+
return super.loadClass(name, resolve);
25+
}
26+
27+
private final Map<String, byte[]> definitions = new HashMap<String, byte[]>();
28+
29+
/** Add a in-memory representation of a class.
30+
* @param name name of the class
31+
* @param bytes array containing class definition
32+
*/
33+
public void addDefinition(final String name, final byte[] bytes) {
34+
definitions.put(name, bytes);
35+
}
36+
37+
/** Looks for a class in memory before delegating to the parent class loader to find the class. */
38+
@Override
39+
protected Class<?> findClass(final String name) throws ClassNotFoundException {
40+
_log.log("MemoryClassLoader.findClass(" + name + ", " + name + ") called");
41+
final byte[] bytes = definitions.get(name);
42+
if (bytes != null) {
43+
_log.log("MemoryClassLoader is loading class " + name + " from memory");
44+
return defineClass(name, bytes, 0, bytes.length); // converts bytes[0:bytes.length) to a Class<?> object
3645
}
46+
_log.log("MemoryClassLoader is throwing a ClassNotFoundException");
47+
throw new ClassNotFoundException("class " + name + " not found by MemoryClassLoader with class definitions for " +
48+
definitions.keySet());
49+
}
3750
}
3851

3952

drjava/src/edu/rice/cs/drjava/model/junit/DefaultJUnitModel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* notice, this list of conditions and the following disclaimer in the
1212
* documentation and/or other materials provided with the distribution.
1313
* * 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
14+
h * names of its contributors may be used to endorse or promote products
1515
* derived from this software without specific prior written permission.
1616
*
1717
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

drjava/src/edu/rice/cs/drjava/model/junit/JUnitTestManager.java

Lines changed: 60 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import edu.rice.cs.plt.iter.IterUtil;
6060
import edu.rice.cs.plt.reflect.ShadowingClassLoader;
6161

62+
import java.lang.reflect.Method;
6263
import java.lang.reflect.Modifier;
6364

6465
import static edu.rice.cs.plt.debug.DebugUtil.debug;
@@ -122,8 +123,24 @@ public JUnitTestManager(JUnitModelCallback jmc, Lambda<ClassLoader, ClassLoader>
122123
}
123124

124125
/** @return result of the last JUnit run */
125-
public JUnitResultTuple getLastResult() {
126-
return this.lastResult;
126+
public JUnitResultTuple getLastResult() { return this.lastResult; }
127+
128+
/** Only called from findTestClasses which sets many fields of this. */
129+
private ClassLoader makeCoverageLoader(ClassLoader parentLoader, final ArrayList<byte[]> instrumenteds) {
130+
final ClassLoader pathLoader = _loaderFactory.value(parentLoader);
131+
final ClassLoader shadowingLoader = ShadowingClassLoader.blackList(pathLoader, classNames);
132+
final MemoryClassLoader memoryLoader = new MemoryClassLoader(shadowingLoader);
133+
for (int i = 0; i < classNames.size(); i++) {
134+
String name = classNames.get(i);
135+
byte[] code = instrumenteds.get(i);
136+
_log.log("Defining class file for '" + name + "' (" + code.length + " bytes) in MemoryClassLoader");
137+
memoryLoader.addDefinition(classNames.get(i), instrumenteds.get(i));
138+
_log.log("Adding definition of class file for " + name + " to MemoryClassLoader");
139+
}
140+
141+
// Need to construct Jacoco class loader with pathLoader as parent! What is fully qualified name of that loader?
142+
// ClassLoader memoryLoader = shadowingLoader;
143+
return memoryLoader;
127144
}
128145

129146
/** Find the test classes among the given classNames and accumulate them in
@@ -133,60 +150,52 @@ public JUnitResultTuple getLastResult() {
133150
* @param coverageMetadata metadata to be used to generate the coverage report
134151
* @return list of test class names
135152
*/
136-
public List<String> findTestClasses(final List<String> classNames,
137-
final List<File> files, CoverageMetadata coverageMetadata) {
153+
public List<String> findTestClasses(final List<String> classNames, final List<File> files,
154+
final CoverageMetadata coverageMetadata) {
138155

139156
_log.log("findTestClasses(" + classNames + ", " + files + ", " + coverageMetadata + ") called");
140157
boolean doCoverage = coverageMetadata.getFlag();
141158

142159
// Set up the loader
160+
final ClassLoader defaultLoader = JUnitTestManager.class.getClassLoader();
161+
final ClassLoader testingLoader = _loaderFactory.value(defaultLoader);
143162
final ClassLoader loader;
144-
if (! doCoverage) {
145-
loader = JUnitTestManager.class.getClassLoader();
146-
} else {
163+
if (! doCoverage) loader = testingLoader;
164+
else {
147165

148-
// JaCoCo: Create instrumented versions of class files.
166+
// JaCoCo: Create instrumented versions of class files and save
149167
this.coverageOutdir = coverageMetadata.getOutdirPath();
150168
this.runtime = new LoggerRuntime();
151169
this.myData = new RuntimeData();
152170
this.classNames = classNames;
153171
this.files = files;
154172
final ArrayList<byte[]> instrumenteds = new ArrayList<byte[]>();
155173

156-
// The Instrumenter creates a modified version of our test target class
157-
// that contains additional probes for execution data recording:
174+
/* The jacoco instrumenter creates a modified version of our test classes that invoke jacoco (as a Java agent)
175+
* to insert byte code to monitor code coverage. */
158176
for (int i = 0 ; i < files.size() ; i++) {
159-
160-
// Instrument the i-th file
161-
try {
162-
final Instrumenter instr = new Instrumenter(this.runtime);
163-
final byte[] instrumented = instr.instrument(
164-
new FileInputStream(files.get(i).getCanonicalPath().
165-
replace(".java", ".class")), classNames.get(i));
166-
String[] pathParts = files.get(i).getAbsolutePath().split("/");
167-
instrumenteds.add(instrumented);
168-
169-
} catch (Exception e) {
170-
StringWriter stackTrace = new StringWriter();
171-
e.printStackTrace(new PrintWriter(stackTrace));
172-
_log.log("Exception during instrumentation: " + stackTrace.toString());
173-
}
174-
}
175-
176-
loader = new MemoryClassLoader(JUnitTestManager.class.getClassLoader());
177-
for (int i = 0; i < classNames.size(); i++) {
178-
String name = classNames.get(i);
179-
byte[] code = instrumenteds.get(i);
180-
_log.log("Loading class file for '" + name + "' consisting of " + code.length + " bytes");
181-
((MemoryClassLoader)loader).addDefinition(classNames.get(i), instrumenteds.get(i));
177+
// Instrument the i-th file
178+
try {
179+
final Instrumenter instr = new Instrumenter(this.runtime);
180+
final byte[] instrumented =
181+
instr.instrument(new FileInputStream(files.get(i).getCanonicalPath().replace(".java", ".class")),
182+
classNames.get(i));
183+
// String[] pathParts = files.get(i).getAbsolutePath().split("/");
184+
instrumenteds.add(instrumented);
185+
} catch (Exception e) {
186+
StringWriter stackTrace = new StringWriter();
187+
e.printStackTrace(new PrintWriter(stackTrace));
188+
_log.log("Exception during instrumentation: " + stackTrace.toString());
189+
}
182190
}
183-
184-
_log.log("Instrumented class files defined in MemoryClassLoader");
185-
try {
186-
this.runtime.startup(myData);
187-
} catch (Exception e) {
188-
_log.log("In code coverage startup, throwing wrapped exception " + e);
189-
throw new UnexpectedException(e);
191+
192+
_log.log("Instrumented test class files for MemoryClassLoader");
193+
194+
loader = makeCoverageLoader(testingLoader, instrumenteds);
195+
try { this.runtime.startup(myData); }
196+
catch (Exception e) {
197+
_log.log("In code coverage startup, throwing the wrapped exception " + e);
198+
throw new UnexpectedException(e);
190199
}
191200
}
192201

@@ -200,6 +209,7 @@ public List<String> findTestClasses(final List<String> classNames,
200209
_testFiles = new ArrayList<File>();
201210
_suite = new TestSuite();
202211

212+
// Assemble test suite (as _suite) and return list of test class names
203213
for (Pair<String, File> pair : IterUtil.zip(classNames, files)) {
204214
String cName = pair.first();
205215
try {
@@ -335,31 +345,26 @@ private void _reset() {
335345
_testFiles = null;
336346
_log.log("test manager state reset");
337347
}
338-
339-
340348

341349
/** Determines if the given class is a junit Test.
342350
* @param c the class to check
343351
* @return true iff the given class is an instance of junit.framework.Test
344352
*/
345353
private boolean _isJUnitTest(Class<?> c) {
346-
boolean isAssignable = Test.class.isAssignableFrom(c);
354+
_log.log("Testing class " + c + " to determine if it is a JUnit test class");
347355
boolean isAbstract = Modifier.isAbstract(c.getModifiers());
348356
boolean isInterface = Modifier.isInterface(c.getModifiers());
349-
JUnit4TestAdapter a = new JUnit4TestAdapter(c);
350-
_log.log("a.getTests() = " + a.getTests());
351-
boolean isJUnit4Test = (a.getTests().size() > 0) && ! a.getTests().get(0).toString().contains("initializationError");
352-
//had to add specific check for initializationError. Is there a better way of checking if a class contains a test?
357+
if (isAbstract || isInterface) return false;
353358

354-
_log.log("isAssignable = " + isAssignable + " isAbstract = " + isAbstract + " isInterface = " + isInterface +
355-
" isJUnit4Test = " + isJUnit4Test);
356-
357-
boolean result = (isAssignable && !isAbstract && !isInterface) || isJUnit4Test;
358-
359-
_log.log("isJUnitTest(" + c + ") = " + result);
360-
return result;
359+
if (Test.class.isAssignableFrom(c)) return true; // JUnit 3 test class
360+
361+
boolean result = false;
362+
for (Method method : Test.class.getDeclaredMethods()) {
363+
if (method.isAnnotationPresent(org.junit.Test.class)) return true;
364+
}
365+
return false;
361366
}
362-
367+
363368
/** Constructs a new JUnitError from a TestFailure
364369
* @param failure A given TestFailure
365370
* @param classNames The classes that were used for this test suite

drjava/src/edu/rice/cs/drjava/model/junit/JUnitTestRunner.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import junit.runner.*;
4040
import junit.framework.*;
4141

42+
import edu.rice.cs.util.Log;
4243
import edu.rice.cs.util.UnexpectedException;
4344

4445
/** DrJava's own testrunner. It updates the document in the JUnit pane as error and failure events are fired.
@@ -47,6 +48,8 @@
4748
*/
4849
public class JUnitTestRunner extends BaseTestRunner {
4950

51+
protected static final Log _log = new Log("JUnitTestManager.txt", true);
52+
5053
/** Receives updates on the test suite's progress. */
5154
private JUnitModelCallback _jmc;
5255

@@ -61,6 +64,8 @@ public class JUnitTestRunner extends BaseTestRunner {
6164

6265
/** The current number of failures in the result. */
6366
private int _failureCount;
67+
68+
6469

6570
/** Standard constructor.
6671
* @param jmc a JUnitModelCallback
@@ -89,7 +94,9 @@ public synchronized TestResult runSuite(TestSuite suite) {
8994
}
9095

9196
public Class<?> loadPossibleTest(String className) throws ClassNotFoundException {
92-
return _loader.loadClass(className);
97+
Class<?> c =_loader.loadClass(className);
98+
_log.log("Test class " + c + " loaded");
99+
return c;
93100
}
94101

95102
@Override protected Class<? extends TestCase> loadSuiteClass(String className) throws ClassNotFoundException {

drjava/src/edu/rice/cs/drjava/model/repl/newjvm/ClassPathManager.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@
4444
import edu.rice.cs.plt.lambda.Lambda;
4545
import edu.rice.cs.plt.reflect.PathClassLoader;
4646

47-
/** * Maintains a dynamic class path, allowing entries to be incrementally added in the appropriate
48-
* place in the list. This class is used in the interpreter JVM, and may be accessed concurrently.
49-
*/
47+
/** Maintains a dynamic class path, allowing entries to be incrementally added in the appropriate
48+
* place in the list. This class is used in the interpreter JVM, and may be accessed concurrently.
49+
*/
5050
public class ClassPathManager implements Lambda<ClassLoader, ClassLoader> {
5151

5252
// For thread safety, all accesses to these lists are synchronized on this, and when they are made available
@@ -160,7 +160,7 @@ public synchronized ClassLoader makeClassLoader(ClassLoader parent) {
160160
return new PathClassLoader(parent, _fullPath);
161161
}
162162

163-
/** Lambda value method */
163+
/** Lambda value method. In DrJava usage, parent is often null. */
164164
public ClassLoader value(ClassLoader parent) { return makeClassLoader(parent); }
165165

166166
/** @return a dynamic view of the full class path. */

0 commit comments

Comments
 (0)