Trac #208: New option classdumpdir for JaCoCo agent
diff --git a/jacoco-maven-plugin.test/it/it-customize-agent/pom.xml b/jacoco-maven-plugin.test/it/it-customize-agent/pom.xml
index 49029cc..f356b7c 100644
--- a/jacoco-maven-plugin.test/it/it-customize-agent/pom.xml
+++ b/jacoco-maven-plugin.test/it/it-customize-agent/pom.xml
@@ -31,6 +31,7 @@
<jacoco.output>file</jacoco.output>
<jacoco.address>localhost</jacoco.address>
<jacoco.port>9999</jacoco.port>
+ <jacoco.classDumpDir>${project.build.directory}/classdumps</jacoco.classDumpDir>
</properties>
<build>
diff --git a/jacoco-maven-plugin.test/it/it-customize-agent/verify.bsh b/jacoco-maven-plugin.test/it/it-customize-agent/verify.bsh
index 230b2d2..7894b0a 100644
--- a/jacoco-maven-plugin.test/it/it-customize-agent/verify.bsh
+++ b/jacoco-maven-plugin.test/it/it-customize-agent/verify.bsh
@@ -21,10 +21,11 @@
+ ",dumponexit=true"
+ ",output=file"
+ ",address=localhost"
- + ",port=9999";
+ + ",port=9999"
+ + ",classdumpdir=" + basedir + File.separator + "target" + File.separator + "classdumps";
String buildLog = FileUtils.fileRead( new File( basedir, "build.log" ) );
if ( buildLog.indexOf( agentOptions ) < 0 ) {
- throw new RuntimeException( "Property was not configured" );
+ throw new RuntimeException( "Property was not configured, expected " + agentOptions );
}
File file = new File( basedir, "target/coverage.exec" );
diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/AgentMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/AgentMojo.java
index 10ff314..06c8d03 100644
--- a/jacoco-maven-plugin/src/org/jacoco/maven/AgentMojo.java
+++ b/jacoco-maven-plugin/src/org/jacoco/maven/AgentMojo.java
@@ -153,6 +153,16 @@
*/
private Integer port;
+ /**
+ * If a directory is specified for this parameter the JaCoCo agent dumps all
+ * class files it processes to the given location. This can be useful for
+ * debugging purposes or in case of dynamically created classes for example
+ * when scripting engines are used.
+ *
+ * @parameter expression="${jacoco.classDumpDir}"
+ */
+ private File classDumpDir;
+
@Override
public void executeMojo() {
final String vmArgument = StringUtils.quoteAndEscape(
@@ -174,8 +184,7 @@
private AgentOptions createAgentOptions() {
final AgentOptions agentOptions = new AgentOptions();
- final String destPath = destFile.getAbsolutePath();
- agentOptions.setDestfile(destPath);
+ agentOptions.setDestfile(destFile.getAbsolutePath());
if (append != null) {
agentOptions.setAppend(append.booleanValue());
}
@@ -207,6 +216,9 @@
if (port != null) {
agentOptions.setPort(port.intValue());
}
+ if (classDumpDir != null) {
+ agentOptions.setClassDumpDir(classDumpDir.getAbsolutePath());
+ }
return agentOptions;
}
diff --git a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/ClassFileDumperTest.java b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/ClassFileDumperTest.java
new file mode 100644
index 0000000..27d997e
--- /dev/null
+++ b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/ClassFileDumperTest.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2012 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.agent.rt;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Unit tests for {@link ClassFileDumper}.
+ */
+public class ClassFileDumperTest {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ private byte[] contents;
+
+ @Before
+ public void setup() throws IOException {
+ contents = "just some bytes".getBytes("UTF-8");
+ }
+
+ @Test
+ public void testDumpClassWithPackage() throws IOException {
+ final File location = new File(folder.getRoot(), "classes");
+ final ClassFileDumper dumper = new ClassFileDumper(location.toString());
+ dumper.dump("org/jacoco/examples/Foo$Inner", contents);
+ assertContents(location, "org/jacoco/examples/Foo$Inner.class");
+ }
+
+ @Test
+ public void testDumpClassInDefaultPackage() throws IOException {
+ final File location = new File(folder.getRoot(), "classes");
+ final ClassFileDumper dumper = new ClassFileDumper(location.toString());
+ dumper.dump("Main", contents);
+ assertContents(location, "Main.class");
+ }
+
+ @Test
+ public void testNoDumps() throws IOException {
+ final ClassFileDumper dumper = new ClassFileDumper(null);
+ dumper.dump("Main", contents);
+ }
+
+ private void assertContents(File location, String filename)
+ throws IOException {
+ InputStream in = new FileInputStream(new File(location, filename));
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ int b;
+ while ((b = in.read()) != -1) {
+ buffer.write(b);
+ }
+ in.close();
+ assertArrayEquals(contents, buffer.toByteArray());
+ }
+
+}
diff --git a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/ClassFileDumper.java b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/ClassFileDumper.java
new file mode 100644
index 0000000..9749ae1
--- /dev/null
+++ b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/ClassFileDumper.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2012 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.agent.rt;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Internal dumper for class files.
+ */
+class ClassFileDumper {
+
+ private final File location;
+
+ /**
+ * Create a new dumper for the given location.
+ *
+ * @param location
+ * relative path to dump directory. <code>null</code> if no dumps
+ * should be written
+ */
+ ClassFileDumper(final String location) {
+ if (location == null) {
+ this.location = null;
+ } else {
+ this.location = new File(location);
+ }
+ }
+
+ /**
+ * Dumps the given binary content under the given name if a non-
+ * <code>null</code> location has been specified.
+ *
+ * @param name
+ * qualified class name in VM notation
+ * @param contents
+ * binary contents
+ * @throws IOException
+ * in case of problems while dumping the file
+ */
+ void dump(final String name, final byte[] contents) throws IOException {
+ if (location != null) {
+ final File outputdir;
+ final String localname;
+ final int pkgpos = name.lastIndexOf('/');
+ if (pkgpos != -1) {
+ outputdir = new File(location, name.substring(0, pkgpos));
+ localname = name.substring(pkgpos + 1);
+ } else {
+ outputdir = location;
+ localname = name;
+ }
+ outputdir.mkdirs();
+ final File file = new File(outputdir, localname + ".class");
+ final OutputStream out = new FileOutputStream(file);
+ out.write(contents);
+ out.close();
+ }
+ }
+
+}
diff --git a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/CoverageTransformer.java b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/CoverageTransformer.java
index 01cb69f..a0b3290 100644
--- a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/CoverageTransformer.java
+++ b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/CoverageTransformer.java
@@ -46,6 +46,8 @@
private final WildcardMatcher exclClassloader;
+ private final ClassFileDumper classFileDumper;
+
/**
* New transformer with the given delegates.
*
@@ -68,6 +70,7 @@
toWildcard(toVMName(options.getExcludes())));
exclClassloader = new WildcardMatcher(
toWildcard(options.getExclClassloader()));
+ classFileDumper = new ClassFileDumper(options.getClassDumpDir());
}
public byte[] transform(final ClassLoader loader, final String classname,
@@ -80,6 +83,7 @@
}
try {
+ classFileDumper.dump(classname, classfileBuffer);
if (classBeingRedefined != null) {
// For redefined classes we must clear the execution data
// reference as probes might have changed.
diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/AgentTaskTest.xml b/org.jacoco.ant.test/src/org/jacoco/ant/AgentTaskTest.xml
index c6874e3..ecbc9d2 100644
--- a/org.jacoco.ant.test/src/org/jacoco/ant/AgentTaskTest.xml
+++ b/org.jacoco.ant.test/src/org/jacoco/ant/AgentTaskTest.xml
@@ -18,7 +18,9 @@
<target name="testCoverageAgent">
<jacoco:agent property="jacocoagent" append="false" destfile="test.exec"
exclClassLoader="EvilClassLoader" includes="org.example.*"
- excludes="*Test" sessionid="testid" dumponexit="false" output="tcpclient" address="remotehost" port="1234"/>
+ excludes="*Test" sessionid="testid" dumponexit="false"
+ output="tcpclient" address="remotehost" port="1234"
+ classdumpdir="target/dump"/>
<au:assertPropertySet name="jacocoagent"/>
<au:assertPropertyContains name="jacocoagent" value="-javaagent:"/>
<au:assertPropertyContains name="jacocoagent" value="append=false"/>
@@ -32,6 +34,8 @@
<au:assertPropertyContains name="jacocoagent" value="output=tcpclient"/>
<au:assertPropertyContains name="jacocoagent" value="address=remotehost"/>
<au:assertPropertyContains name="jacocoagent" value="port=1234"/>
+ <property name="dump.dir" location="target/dump"/>
+ <au:assertPropertyContains name="jacocoagent" value="classdumpdir=${dump.dir}"/>
</target>
<target name="testCoverageAgentDisabled">
diff --git a/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java b/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java
index 4f94eb5..6c8d37c 100644
--- a/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java
+++ b/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java
@@ -172,6 +172,17 @@
}
/**
+ * Sets the directory where all class files seen by the agent should be
+ * dumped to.
+ *
+ * @param dir
+ * dump output location
+ */
+ public void setClassdumpdir(final File dir) {
+ agentOptions.setClassDumpDir(dir.getAbsolutePath());
+ }
+
+ /**
* Creates JVM argument to launch with the specified JaCoCo agent jar and
* the current options
*
diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/AgentOptionsTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/AgentOptionsTest.java
index 63ff910..52b77a5 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/AgentOptionsTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/AgentOptionsTest.java
@@ -45,9 +45,10 @@
assertNull(options.getSessionId());
assertTrue(options.getDumpOnExit());
assertEquals(6300, options.getPort());
- assertEquals(null, options.getAddress());
+ assertNull(options.getAddress());
assertEquals(AgentOptions.OutputMode.file, options.getOutput());
assertEquals("", options.toString());
+ assertNull(options.getClassDumpDir());
}
@Test
@@ -273,6 +274,20 @@
}
@Test
+ public void testGetClassDumpDir() {
+ AgentOptions options = new AgentOptions("classdumpdir=target/dump");
+ assertEquals("target/dump", options.getClassDumpDir());
+ }
+
+ @Test
+ public void testSetClassDumpDir() {
+ AgentOptions options = new AgentOptions();
+ options.setClassDumpDir("target/dump");
+ assertEquals("target/dump", options.getClassDumpDir());
+ assertEquals("classdumpdir=target/dump", options.toString());
+ }
+
+ @Test
public void testVMArgsWithNoOptions() {
AgentOptions options = new AgentOptions();
String vmArgument = options.getVMArgument(defaultAgentJarFile);
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java
index cc828fe..16067a6 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java
@@ -148,9 +148,16 @@
*/
public static final int DEFAULT_PORT = 6300;
+ /**
+ * Specifies where the agent dumps all class files it encounters. The
+ * location is specified as a relative path to the working directory.
+ * Default is <code>null</code> (no dumps).
+ */
+ public static final String CLASSDUMPDIR = "classdumpdir";
+
private static final Collection<String> VALID_OPTIONS = Arrays.asList(
DESTFILE, APPEND, INCLUDES, EXCLUDES, EXCLCLASSLOADER, SESSIONID,
- DUMPONEXIT, OUTPUT, ADDRESS, PORT);
+ DUMPONEXIT, OUTPUT, ADDRESS, PORT, CLASSDUMPDIR);
private final Map<String, String> options;
@@ -415,6 +422,26 @@
setOption(OUTPUT, output.name());
}
+ /**
+ * Returns the location of the directory where class files should be dumped
+ * to.
+ *
+ * @return dump location or <code>null</code> (no dumps)
+ */
+ public String getClassDumpDir() {
+ return getOption(CLASSDUMPDIR, null);
+ }
+
+ /**
+ * Sets the directory where class files should be dumped to.
+ *
+ * @param location
+ * dump location or <code>null</code> (no dumps)
+ */
+ public void setClassDumpDir(final String location) {
+ setOption(CLASSDUMPDIR, location);
+ }
+
private void setOption(final String key, final int value) {
setOption(key, Integer.toString(value));
}
diff --git a/org.jacoco.doc/docroot/doc/agent.html b/org.jacoco.doc/docroot/doc/agent.html
index 79c5003..f5cf866 100644
--- a/org.jacoco.doc/docroot/doc/agent.html
+++ b/org.jacoco.doc/docroot/doc/agent.html
@@ -167,6 +167,15 @@
</td>
<td><code>6300</code></td>
</tr>
+ <tr>
+ <td><code>classdumpdir</code></td>
+ <td>Location relative to the working directory where all class files seen
+ by the agent are dumped to. This can be useful for debugging purposes
+ or in case of dynamically created classes for example when scripting
+ engines are used.
+ </td>
+ <td><i>no dumps</i></td>
+ </tr>
</tbody>
</table>
diff --git a/org.jacoco.doc/docroot/doc/ant.html b/org.jacoco.doc/docroot/doc/ant.html
index 38d8bdd..4da2fff 100644
--- a/org.jacoco.doc/docroot/doc/ant.html
+++ b/org.jacoco.doc/docroot/doc/ant.html
@@ -254,6 +254,15 @@
</td>
<td><code>6300</code></td>
</tr>
+ <tr>
+ <td><code>classdumpdir</code></td>
+ <td>Location relative to the working directory where all class files seen
+ by the agent are dumped to. This can be useful for debugging purposes
+ or in case of dynamically created classes for example when scripting
+ engines are used.
+ </td>
+ <td><i>no dumps</i></td>
+ </tr>
</tbody>
</table>
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 1c3f073..683cb6e 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -20,11 +20,18 @@
<h2>Trunk Build @qualified.bundle.version@ (@build.date@)</h2>
+<h3>New Features</h3>
+<ul>
+ <li>Support for parallel Maven builds (Trac #191).</li>
+ <li>New agent option <code>classdumpdir</code> to dump all class files seen
+ by the JaCoCo agent to disk. This option is also available for Ant and
+ Maven (Trac #208).</li>
+</ul>
+
<h3>Non-functional Changes</h3>
<ul>
<li>Documentation now includes Maven example and Maven goal documentation
(Trac #201, #202).</li>
- <li>Support for parallel Maven builds (Trac #191).</li>
<li>Reworked instrumentation strategy to avoid verifier error "Uninitialized
object exists on backward branch" with certain Java 7 class files
(Trac #154).</li>