diff options
13 files changed, 194 insertions, 17 deletions
diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java index 4ed00c54..ee38381f 100644 --- a/jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java +++ b/jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java @@ -73,6 +73,14 @@ public abstract class AbstractAgentMojo extends AbstractJacocoMojo { */ String exclClassLoaders; /** + * Specifies whether also classes from the bootstrap classloader should be + * instrumented. Use this feature with caution, it needs heavy + * includes/excludes tuning. + * + * @parameter property="jacoco.includebootstrapclasses" + */ + Boolean includebootstrapclasses; + /** * A session identifier that is written with the execution data. Without * this parameter a random identifier is created by the agent. * @@ -178,6 +186,10 @@ public abstract class AbstractAgentMojo extends AbstractJacocoMojo { if (exclClassLoaders != null) { agentOptions.setExclClassloader(exclClassLoaders); } + if (includebootstrapclasses != null) { + agentOptions.setIncludeBootstrapClasses(includebootstrapclasses + .booleanValue()); + } if (sessionId != null) { agentOptions.setSessionId(sessionId); } diff --git a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/internal/CoverageTransformerTest.java b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/internal/CoverageTransformerTest.java index 15e3d1a5..09b97aa3 100644 --- a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/internal/CoverageTransformerTest.java +++ b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/internal/CoverageTransformerTest.java @@ -64,9 +64,17 @@ public class CoverageTransformerTest { } @Test - public void testFilterSystemClass() { + public void testFilterIncludesBootstrapClassesPositive() { + options.setIncludeBootstrapClasses(true); CoverageTransformer t = createTransformer(); - assertFalse(t.filter(null, "org/example/Foo")); + assertTrue(t.filter(null, "java/util/TreeSet")); + } + + @Test + public void testFilterIncludesBootstrapClassesNegative() { + options.setIncludeBootstrapClasses(false); + CoverageTransformer t = createTransformer(); + assertFalse(t.filter(null, "java/util/TreeSet")); } @Test diff --git a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/CoverageTransformer.java b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/CoverageTransformer.java index 78d7b5e5..8b14a237 100644 --- a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/CoverageTransformer.java +++ b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/CoverageTransformer.java @@ -44,6 +44,8 @@ public class CoverageTransformer implements ClassFileTransformer { private final ClassFileDumper classFileDumper; + private final boolean includeBootstrapClasses; + /** * New transformer with the given delegates. * @@ -63,6 +65,7 @@ public class CoverageTransformer implements ClassFileTransformer { excludes = new WildcardMatcher(toVMName(options.getExcludes())); exclClassloader = new WildcardMatcher(options.getExclClassloader()); classFileDumper = new ClassFileDumper(options.getClassDumpDir()); + includeBootstrapClasses = options.getIncludeBootstrapClasses(); } public byte[] transform(final ClassLoader loader, final String classname, @@ -102,12 +105,16 @@ public class CoverageTransformer implements ClassFileTransformer { * @return <code>true</code> if the class should be instrumented */ protected boolean filter(final ClassLoader loader, final String classname) { - // Don't instrument classes of the bootstrap loader: - return loader != null && - - !classname.startsWith(AGENT_PREFIX) && + if (!includeBootstrapClasses) { + if (loader == null) { + return false; + } + if (exclClassloader.matches(loader.getClass().getName())) { + return false; + } + } - !exclClassloader.matches(loader.getClass().getName()) && + return !classname.startsWith(AGENT_PREFIX) && includes.matches(classname) && diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/CoverageTaskTest.xml b/org.jacoco.ant.test/src/org/jacoco/ant/CoverageTaskTest.xml index c6684f13..0afab42a 100644 --- a/org.jacoco.ant.test/src/org/jacoco/ant/CoverageTaskTest.xml +++ b/org.jacoco.ant.test/src/org/jacoco/ant/CoverageTaskTest.xml @@ -18,7 +18,7 @@ <target name="setUp"> <tempfile property="temp.dir" prefix="jacocoTest" destdir="${java.io.tmpdir}" /> <mkdir dir="${temp.dir}"/> - <property name="exec.file" location="${temp.dir}/exec.file" /> + <property name="exec.file" location="${temp.dir}/jacoco.exec" /> </target> <target name="tearDown"> @@ -141,5 +141,22 @@ <au:assertLogDoesntContain text="Target executed"/> </target> - + + <target name="testIncludeBootstrapClasses"> + <jacoco:coverage destfile="${exec.file}" includebootstrapclasses="true" includes="java/sql/*"> + <java classname="org.jacoco.ant.TestTarget" fork="true" failonerror="true"> + <classpath path="${org.jacoco.ant.coverageTaskTest.classes.dir}"/> + </java> + </jacoco:coverage> + + <au:assertLogContains text="Enhancing java with coverage"/> + <au:assertFileExists file="${exec.file}"/> + <au:assertLogContains text="Target executed"/> + + <java classname="org.jacoco.ant.DumpExecClassNames" classpath="${java.class.path}" failonerror="true"> + <arg value="${exec.file}" /> + </java> + <au:assertLogContains text="java/sql/Timestamp"/> + </target> + </project>
\ No newline at end of file diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/DumpExecClassNames.java b/org.jacoco.ant.test/src/org/jacoco/ant/DumpExecClassNames.java new file mode 100644 index 00000000..60133b09 --- /dev/null +++ b/org.jacoco.ant.test/src/org/jacoco/ant/DumpExecClassNames.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2009, 2014 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.ant; + +import java.io.File; + +import org.jacoco.core.data.ExecutionData; +import org.jacoco.core.tools.ExecFileLoader; + +/** + * Test utility to dump class names from exec file. + */ +public class DumpExecClassNames { + + public static void main(String[] args) throws Exception { + final ExecFileLoader loader = new ExecFileLoader(); + for (String f : args) { + loader.load(new File(f)); + } + for (ExecutionData d : loader.getExecutionDataStore().getContents()) { + System.out.println(d.getName()); + } + } + +} diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml index dbf616d1..345c9db7 100644 --- a/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml +++ b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml @@ -64,7 +64,7 @@ <jacoco:instrument destdir="${instr.dir}"> <fileset dir="${lib.dir}" includes="*.jar"/> </jacoco:instrument> - <au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/> + <au:assertLogContains text="Instrumented 13 classes to ${temp.dir}"/> <unzip src="${instr.dir}/test.jar" dest="${instr.dir}"/> <au:assertFileDoesntExist file="${instr.dir}/META-INF/TEST.RSA" /> @@ -85,7 +85,7 @@ <jacoco:instrument destdir="${instr.dir}" removesignatures="false"> <fileset dir="${lib.dir}" includes="*.jar"/> </jacoco:instrument> - <au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/> + <au:assertLogContains text="Instrumented 13 classes to ${temp.dir}"/> <unzip src="${instr.dir}/test.jar" dest="${instr.dir}"/> <au:assertFileExists file="${instr.dir}/META-INF/TEST.RSA" /> @@ -96,7 +96,7 @@ <jacoco:instrument destdir="${temp.dir}"> <fileset dir="${org.jacoco.ant.instrumentTaskTest.classes.dir}" includes="**/*.class"/> </jacoco:instrument> - <au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/> + <au:assertLogContains text="Instrumented 13 classes to ${temp.dir}"/> <au:assertFileExists file="${temp.dir}/org/jacoco/ant/InstrumentTaskTest.class" /> <echo file="${temp.dir}/jacoco-agent.properties">destfile=test.exec</echo> @@ -113,7 +113,7 @@ <jacoco:instrument destdir="${temp.dir}"> <fileset dir="${org.jacoco.ant.instrumentTaskTest.classes.dir}" includes="**/*.class"/> </jacoco:instrument> - <au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/> + <au:assertLogContains text="Instrumented 13 classes to ${temp.dir}"/> <au:assertFileExists file="${temp.dir}/org/jacoco/ant/InstrumentTaskTest.class" /> <java classname="org.jacoco.ant.TestTarget" failonerror="true" fork="true"> diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/TestTarget.java b/org.jacoco.ant.test/src/org/jacoco/ant/TestTarget.java index 5b5023eb..9bc91b67 100644 --- a/org.jacoco.ant.test/src/org/jacoco/ant/TestTarget.java +++ b/org.jacoco.ant.test/src/org/jacoco/ant/TestTarget.java @@ -31,6 +31,10 @@ public class TestTarget { } public static void main(String[] args) throws Exception { + + // Load some class from the bootstrap classloader: + new java.sql.Timestamp(0); + System.out.println("Target executed"); // Wait for termination file to turn up diff --git a/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java b/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java index 18ef70c1..8a9cbb70 100644 --- a/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java +++ b/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java @@ -120,6 +120,17 @@ public class AbstractCoverageTask extends Task { } /** + * Sets whether classes from the bootstrap classloader should be + * instrumented. + * + * @param enabled + * <code>true</code> if bootstrap classes should be instrumented + */ + public void setIncludeBootstrapClasses(final boolean enabled) { + agentOptions.setIncludeBootstrapClasses(enabled); + } + + /** * Sets the session identifier. Default is a auto-generated id * * @param id 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 e64beaa0..75e7641f 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 @@ -43,6 +43,7 @@ public class AgentOptionsTest { assertEquals("", options.getExcludes()); assertEquals("sun.reflect.DelegatingClassLoader", options.getExclClassloader()); + assertFalse(options.getIncludeBootstrapClasses()); assertNull(options.getSessionId()); assertTrue(options.getDumpOnExit()); assertEquals(AgentOptions.OutputMode.file, options.getOutput()); @@ -74,6 +75,7 @@ public class AgentOptionsTest { properties.put("includes", "org.*:com.*"); properties.put("excludes", "*Test"); properties.put("exclclassloader", "org.jacoco.test.TestLoader"); + properties.put("includebootstrapclasses", "true"); properties.put("sessionid", "testsession"); properties.put("dumponexit", "false"); properties.put("output", "tcpserver"); @@ -89,6 +91,7 @@ public class AgentOptionsTest { assertEquals("org.*:com.*", options.getIncludes()); assertEquals("*Test", options.getExcludes()); assertEquals("org.jacoco.test.TestLoader", options.getExclClassloader()); + assertTrue(options.getIncludeBootstrapClasses()); assertEquals("testsession", options.getSessionId()); assertFalse(options.getDumpOnExit()); assertEquals(AgentOptions.OutputMode.tcpserver, options.getOutput()); @@ -185,6 +188,34 @@ public class AgentOptionsTest { } @Test + public void testGetIncludeBootstrapClassesTrue() { + AgentOptions options = new AgentOptions("includebootstrapclasses=true"); + assertTrue(options.getIncludeBootstrapClasses()); + } + + @Test + public void testGetIncludeBootstrapClassesFalse() { + AgentOptions options = new AgentOptions("includebootstrapclasses=false"); + assertFalse(options.getIncludeBootstrapClasses()); + } + + @Test + public void testSetIncludeBootstrapClassesTrue() { + AgentOptions options = new AgentOptions(); + options.setIncludeBootstrapClasses(true); + assertTrue(options.getIncludeBootstrapClasses()); + assertEquals("includebootstrapclasses=true", options.toString()); + } + + @Test + public void testSetIncludeBootstrapClassesFalse() { + AgentOptions options = new AgentOptions(); + options.setIncludeBootstrapClasses(false); + assertFalse(options.getIncludeBootstrapClasses()); + assertEquals("includebootstrapclasses=false", options.toString()); + } + + @Test public void testGetSessionId() { AgentOptions options = new AgentOptions("sessionid=testsession"); assertEquals("testsession", options.getSessionId()); 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 f45a35a5..a36fe4fc 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java @@ -72,6 +72,13 @@ public final class AgentOptions { public static final String EXCLCLASSLOADER = "exclclassloader"; /** + * Specifies whether also classes from the bootstrap classloader should be + * instrumented. Use this feature with caution, it needs heavy + * includes/excludes tuning. Default is <code>false</code>. + */ + public static final String INCLUDEBOOTSTRAPCLASSES = "includebootstrapclasses"; + + /** * Specifies a session identifier that is written with the execution data. * Without this parameter a random identifier is created by the agent. */ @@ -165,8 +172,9 @@ public final class AgentOptions { public static final String JMX = "jmx"; private static final Collection<String> VALID_OPTIONS = Arrays.asList( - DESTFILE, APPEND, INCLUDES, EXCLUDES, EXCLCLASSLOADER, SESSIONID, - DUMPONEXIT, OUTPUT, ADDRESS, PORT, CLASSDUMPDIR, JMX); + DESTFILE, APPEND, INCLUDES, EXCLUDES, EXCLCLASSLOADER, + INCLUDEBOOTSTRAPCLASSES, SESSIONID, DUMPONEXIT, OUTPUT, ADDRESS, + PORT, CLASSDUMPDIR, JMX); private final Map<String, String> options; @@ -335,6 +343,27 @@ public final class AgentOptions { } /** + * Returns whether classes from the bootstrap classloader should be + * instrumented. + * + * @return <code>true</code> if coverage data will be written on VM exit + */ + public boolean getIncludeBootstrapClasses() { + return getOption(INCLUDEBOOTSTRAPCLASSES, false); + } + + /** + * Sets whether classes from the bootstrap classloader should be + * instrumented. + * + * @param enabled + * <code>true</code> if bootstrap classes should be instrumented + */ + public void setIncludeBootstrapClasses(final boolean enabled) { + setOption(INCLUDEBOOTSTRAPCLASSES, enabled); + } + + /** * Returns the session identifier. * * @return session identifier @@ -354,7 +383,7 @@ public final class AgentOptions { } /** - * Returns whether coverage data should be dumped on exit + * Returns whether coverage data should be dumped on exit. * * @return <code>true</code> if coverage data will be written on VM exit */ @@ -363,7 +392,7 @@ public final class AgentOptions { } /** - * Sets whether coverage data should be dumped on exit + * Sets whether coverage data should be dumped on exit. * * @param dumpOnExit * <code>true</code> if coverage data should be written on VM diff --git a/org.jacoco.doc/docroot/doc/agent.html b/org.jacoco.doc/docroot/doc/agent.html index c3dec5f9..5a1628d8 100644 --- a/org.jacoco.doc/docroot/doc/agent.html +++ b/org.jacoco.doc/docroot/doc/agent.html @@ -117,6 +117,14 @@ <td><code>sun.reflect.DelegatingClassLoader</code></td> </tr> <tr> + <td><code>includebootstrapclasses</code></td> + <td>Specifies whether also classes from the bootstrap classloader should + be instrumented. Use this feature with caution, it needs heavy + includes/excludes tuning. + </td> + <td><code>false</code></td> + </tr> + <tr> <td><code>sessionid</code></td> <td>A session identifier that is written with the execution data. Without this parameter a random identifier is created by the agent. diff --git a/org.jacoco.doc/docroot/doc/ant.html b/org.jacoco.doc/docroot/doc/ant.html index 7400b7d5..aac3473f 100644 --- a/org.jacoco.doc/docroot/doc/ant.html +++ b/org.jacoco.doc/docroot/doc/ant.html @@ -205,6 +205,14 @@ <td><code>sun.reflect.DelegatingClassLoader</code></td> </tr> <tr> + <td><code>includebootstrapclasses</code></td> + <td>Specifies whether also classes from the bootstrap classloader should + be instrumented. Use this feature with caution, it needs heavy + includes/excludes tuning. + </td> + <td><code>false</code></td> + </tr> + <tr> <td><code>sessionid</code></td> <td>A session identifier that is written with the execution data. Without this parameter a random identifier is created by the agent. diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index 51679b67..9d5209ac 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -20,6 +20,14 @@ <h2>Snapshot Build @qualified.bundle.version@ (@build.date@)</h2> +<h3>New Features</h3> +<ul> + <li>New configuration option for the JaCoCo agent + <code>includebootstrapclasses</code> to also instrument classes from the + bootstrap class loader. + (GitHub <a href="https://github.com/jacoco/jacoco/issues/49">#49</a>).</li> +</ul> + <h2>Release 0.7.1 (2014/05/08)</h2> <h3>Fixed Bugs</h3> |