Skip to content

Commit

Permalink
GitHub #288: New agent option 'inclnolocationclasses'
Browse files Browse the repository at this point in the history
With the new agent option 'inclnolocationclasses' classes without source
location can be optionally included. This helps to retrieve code
coverage for environments like Android where no source location is
provided at runtime.
  • Loading branch information
marchof committed Jan 13, 2016
1 parent a6d2b04 commit 26daee4
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 85 deletions.
3 changes: 3 additions & 0 deletions jacoco-maven-plugin.test/it/it-customize-agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
<jacoco.destFile>${project.build.directory}/coverage.exec</jacoco.destFile>
<jacoco.append>false</jacoco.append>
<jacoco.exclClassLoaders>sun.reflect.DelegatingClassLoader:MyClassLoader</jacoco.exclClassLoaders>
<jacoco.inclBootstrapClasses>true</jacoco.inclBootstrapClasses>
<jacoco.inclNoLocationClasses>true</jacoco.inclNoLocationClasses>
<jacoco.sessionId>session</jacoco.sessionId>
<jacoco.dumpOnExit>true</jacoco.dumpOnExit>
<jacoco.output>file</jacoco.output>
<jacoco.address>localhost</jacoco.address>
<jacoco.port>9999</jacoco.port>
<jacoco.classDumpDir>${project.build.directory}/classdumps</jacoco.classDumpDir>
<jacoco.jmx>true</jacoco.jmx>
</properties>

<build>
Expand Down
5 changes: 4 additions & 1 deletion jacoco-maven-plugin.test/it/it-customize-agent/verify.bsh
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ String agentOptions = "destfile=" + basedir + File.separator + "target" + File.s
+ ",includes=*"
+ ",excludes=java.*:sun.*"
+ ",exclclassloader=sun.reflect.DelegatingClassLoader:MyClassLoader"
+ ",inclbootstrapclasses=true"
+ ",inclnolocationclasses=true"
+ ",sessionid=session"
+ ",dumponexit=true"
+ ",output=file"
+ ",address=localhost"
+ ",port=9999"
+ ",classdumpdir=" + basedir + File.separator + "target" + File.separator + "classdumps";
+ ",classdumpdir=" + basedir + File.separator + "target" + File.separator + "classdumps"
+ ",jmx=true";

//backslashes will be escaped
agentOptions = agentOptions.replace("\\","\\\\");
Expand Down
10 changes: 10 additions & 0 deletions jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ public abstract class AbstractAgentMojo extends AbstractJacocoMojo {
* @parameter property="jacoco.inclBootstrapClasses"
*/
Boolean inclBootstrapClasses;
/**
* Specifies whether classes without source location should be instrumented.
*
* @parameter property="jacoco.inclNoLocationClasses"
*/
Boolean inclNoLocationClasses;
/**
* A session identifier that is written with the execution data. Without
* this parameter a random identifier is created by the agent.
Expand Down Expand Up @@ -190,6 +196,10 @@ AgentOptions createAgentOptions() {
agentOptions.setInclBootstrapClasses(inclBootstrapClasses
.booleanValue());
}
if (inclNoLocationClasses != null) {
agentOptions.setInclNoLocationClasses(inclNoLocationClasses
.booleanValue());
}
if (sessionId != null) {
agentOptions.setSessionId(sessionId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,68 +62,42 @@ public void teardown() {
recorder.assertNoException();
}

@Test
public void testHasSourceLocationNegative1() {
CoverageTransformer t = createTransformer();
assertFalse(t.hasSourceLocation(null));
}

@Test
public void testHasSourceLocationNegative2() {
CoverageTransformer t = createTransformer();
ProtectionDomain pd = new ProtectionDomain(null, null);
assertFalse(t.hasSourceLocation(pd));
}

@Test
public void testHasSourceLocationNegative3() {
CoverageTransformer t = createTransformer();
CodeSource cs = new CodeSource(null, new Certificate[0]);
ProtectionDomain pd = new ProtectionDomain(cs, null);
assertFalse(t.hasSourceLocation(pd));
}

@Test
public void testHasSourceLocationPositive() {
CoverageTransformer t = createTransformer();
assertTrue(t.hasSourceLocation(protectionDomain));
}

@Test
public void testFilterAgentClass() {
CoverageTransformer t = createTransformer();
assertFalse(t.filter(classLoader,
"org/jacoco/agent/rt/internal/somepkg/SomeClass"));
"org/jacoco/agent/rt/internal/somepkg/SomeClass",
protectionDomain));
}

@Test
public void testFilterIncludesBootstrapClassesPositive() {
public void testFilterInclBootstrapClassesPositive() {
options.setInclBootstrapClasses(true);
CoverageTransformer t = createTransformer();
assertTrue(t.filter(null, "java/util/TreeSet"));
assertTrue(t.filter(null, "java/util/TreeSet", protectionDomain));
}

@Test
public void testFilterIncludesBootstrapClassesNegative() {
public void testFilterInclBootstrapClassesNegative() {
options.setInclBootstrapClasses(false);
CoverageTransformer t = createTransformer();
assertFalse(t.filter(null, "java/util/TreeSet"));
assertFalse(t.filter(null, "java/util/TreeSet", protectionDomain));
}

@Test
public void testFilterClassLoaderPositive1() {
options.setInclBootstrapClasses(false);
options.setExclClassloader("org.jacoco.agent.SomeWhere$*");
CoverageTransformer t = createTransformer();
assertTrue(t.filter(classLoader, "org/example/Foo"));
assertTrue(t.filter(classLoader, "org/example/Foo", protectionDomain));
}

@Test
public void testFilterClassLoaderPositive2() {
options.setInclBootstrapClasses(true);
options.setExclClassloader("org.jacoco.agent.SomeWhere$*");
CoverageTransformer t = createTransformer();
assertTrue(t.filter(classLoader, "org/example/Foo"));
assertTrue(t.filter(classLoader, "org/example/Foo", protectionDomain));
}

@Test
Expand All @@ -133,7 +107,8 @@ public void testFilterClassLoaderNegative1() {
CoverageTransformer t = createTransformer();
ClassLoader myClassLoader = new ClassLoader(null) {
};
assertFalse(t.filter(myClassLoader, "org/example/Foo"));
assertFalse(t
.filter(myClassLoader, "org/example/Foo", protectionDomain));
}

@Test
Expand All @@ -143,42 +118,76 @@ public void testFilterClassLoaderNegative2() {
CoverageTransformer t = createTransformer();
ClassLoader myClassLoader = new ClassLoader(null) {
};
assertFalse(t.filter(myClassLoader, "org/example/Foo"));
assertFalse(t
.filter(myClassLoader, "org/example/Foo", protectionDomain));
}

@Test
public void testFilterIncludedClassPositive() {
options.setIncludes("org.jacoco.core.*:org.jacoco.agent.rt.*");
CoverageTransformer t = createTransformer();
assertTrue(t.filter(classLoader, "org/jacoco/core/Foo"));
assertTrue(t.filter(classLoader, "org/jacoco/core/Foo",
protectionDomain));
}

@Test
public void testFilterIncludedClassNegative() {
options.setIncludes("org.jacoco.core.*:org.jacoco.agent.rt.*");
CoverageTransformer t = createTransformer();
assertFalse(t.filter(classLoader, "org/jacoco/report/Foo"));
assertFalse(t.filter(classLoader, "org/jacoco/report/Foo",
protectionDomain));
}

@Test
public void testFilterExcludedClassPositive() {
options.setExcludes("*Test");
CoverageTransformer t = createTransformer();
assertFalse(t.filter(classLoader, "org/jacoco/core/FooTest"));
assertFalse(t.filter(classLoader, "org/jacoco/core/FooTest",
protectionDomain));
}

@Test
public void testFilterExcludedClassPositiveInner() {
options.setExcludes("org.jacoco.example.Foo$Inner");
CoverageTransformer t = createTransformer();
assertFalse(t.filter(classLoader, "org/jacoco/example/Foo$Inner"));
assertFalse(t.filter(classLoader, "org/jacoco/example/Foo$Inner",
protectionDomain));
}

@Test
public void testFilterExcludedClassNegative() {
options.setExcludes("*Test");
CoverageTransformer t = createTransformer();
assertTrue(t.filter(classLoader, "org/jacoco/core/Foo"));
assertTrue(t.filter(classLoader, "org/jacoco/core/Foo",
protectionDomain));
}

@Test
public void testFilterSourceLocationPositive1() {
CoverageTransformer t = createTransformer();
assertFalse(t.filter(classLoader, "org/jacoco/core/Foo", null));
}

@Test
public void testFilterSourceLocationPositive2() {
CoverageTransformer t = createTransformer();
ProtectionDomain pd = new ProtectionDomain(null, null);
assertFalse(t.filter(classLoader, "org/jacoco/core/Foo", pd));
}

@Test
public void testFilterSourceLocationPositive3() {
CoverageTransformer t = createTransformer();
CodeSource cs = new CodeSource(null, new Certificate[0]);
ProtectionDomain pd = new ProtectionDomain(cs, null);
assertFalse(t.filter(classLoader, "org/jacoco/core/Foo", pd));
}

@Test
public void testFilterSourceLocationNegative() {
options.setInclNoLocationClasses(true);
CoverageTransformer t = createTransformer();
assertTrue(t.filter(classLoader, "org/jacoco/core/Foo", null));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ public class CoverageTransformer implements ClassFileTransformer {

private final ClassFileDumper classFileDumper;

private final boolean includeBootstrapClasses;
private final boolean inclBootstrapClasses;

private final boolean inclNoLocationClasses;

/**
* New transformer with the given delegates.
Expand All @@ -66,7 +68,8 @@ public CoverageTransformer(final IRuntime runtime,
excludes = new WildcardMatcher(toVMName(options.getExcludes()));
exclClassloader = new WildcardMatcher(options.getExclClassloader());
classFileDumper = new ClassFileDumper(options.getClassDumpDir());
includeBootstrapClasses = options.getInclBootstrapClasses();
inclBootstrapClasses = options.getInclBootstrapClasses();
inclNoLocationClasses = options.getInclNoLocationClasses();
}

public byte[] transform(final ClassLoader loader, final String classname,
Expand All @@ -79,12 +82,7 @@ public byte[] transform(final ClassLoader loader, final String classname,
return null;
}

// We exclude dynamically created non-bootstrap classes.
if (loader != null && !hasSourceLocation(protectionDomain)) {
return null;
}

if (!filter(loader, classname)) {
if (!filter(loader, classname, protectionDomain)) {
return null;
}

Expand All @@ -101,40 +99,27 @@ public byte[] transform(final ClassLoader loader, final String classname,
}
}

/**
* Checks whether this protection domain is associated with a source
* location.
*
* @param protectionDomain
* protection domain to check (or <code>null</code>)
* @return <code>true</code> if a source location is defined
*/
boolean hasSourceLocation(final ProtectionDomain protectionDomain) {
if (protectionDomain == null) {
return false;
}
final CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource == null) {
return false;
}
return codeSource.getLocation() != null;
}

/**
* Checks whether this class should be instrumented.
*
* @param loader
* loader for the class
* @param classname
* VM name of the class to check
* @param protectionDomain
* protection domain for the class
* @return <code>true</code> if the class should be instrumented
*/
boolean filter(final ClassLoader loader, final String classname) {
boolean filter(final ClassLoader loader, final String classname,
final ProtectionDomain protectionDomain) {
if (loader == null) {
if (!includeBootstrapClasses) {
if (!inclBootstrapClasses) {
return false;
}
} else {
if (!inclNoLocationClasses && !hasSourceLocation(protectionDomain)) {
return false;
}
if (exclClassloader.matches(loader.getClass().getName())) {
return false;
}
Expand All @@ -147,6 +132,25 @@ boolean filter(final ClassLoader loader, final String classname) {
!excludes.matches(classname);
}

/**
* Checks whether this protection domain is associated with a source
* location.
*
* @param protectionDomain
* protection domain to check (or <code>null</code>)
* @return <code>true</code> if a source location is defined
*/
private boolean hasSourceLocation(final ProtectionDomain protectionDomain) {
if (protectionDomain == null) {
return false;
}
final CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource == null) {
return false;
}
return codeSource.getLocation() != null;
}

private static String toVMName(final String srcName) {
return srcName.replace('.', '/');
}
Expand Down
10 changes: 7 additions & 3 deletions org.jacoco.ant.test/src/org/jacoco/ant/AgentTaskTest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@

<target name="testCoverageAgent">
<jacoco:agent property="jacocoagent" append="false" destfile="test.exec"
exclClassLoader="EvilClassLoader" includes="org.example.*"
excludes="*Test" sessionid="testid" dumponexit="false"
output="file" address="remotehost" port="1234"
exclClassLoader="EvilClassLoader" includes="org.example.*" excludes="*Test"
inclbootstrapclasses="true" inclnolocationclasses="true"
sessionid="testid" dumponexit="false"
output="file" address="remotehost" port="1234" jmx="true"
classdumpdir="target/dump"/>
<au:assertPropertySet name="jacocoagent"/>
<au:assertPropertyContains name="jacocoagent" value="-javaagent:"/>
Expand All @@ -29,11 +30,14 @@
<au:assertPropertyContains name="jacocoagent" value="exclclassloader=EvilClassLoader"/>
<au:assertPropertyContains name="jacocoagent" value="includes=org.example.*"/>
<au:assertPropertyContains name="jacocoagent" value="excludes=*Test"/>
<au:assertPropertyContains name="jacocoagent" value="inclbootstrapclasses=true"/>
<au:assertPropertyContains name="jacocoagent" value="inclnolocationclasses=true"/>
<au:assertPropertyContains name="jacocoagent" value="sessionid=testid"/>
<au:assertPropertyContains name="jacocoagent" value="dumponexit=false"/>
<au:assertPropertyContains name="jacocoagent" value="output=file"/>
<au:assertPropertyContains name="jacocoagent" value="address=remotehost"/>
<au:assertPropertyContains name="jacocoagent" value="port=1234"/>
<au:assertPropertyContains name="jacocoagent" value="jmx=true"/>
<property name="dump.dir" location="target/dump"/>
<au:assertPropertyContains name="jacocoagent" value="classdumpdir=${dump.dir}"/>
</target>
Expand Down
11 changes: 11 additions & 0 deletions org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ public void setInclBootstrapClasses(final boolean include) {
agentOptions.setInclBootstrapClasses(include);
}

/**
* Sets whether classes without source location should be instrumented.
*
* @param include
* <code>true</code> if classes without source location should be
* instrumented
*/
public void setInclNoLocationClasses(final boolean include) {
agentOptions.setInclNoLocationClasses(include);
}

/**
* Sets the session identifier. Default is a auto-generated id
*
Expand Down
Loading

0 comments on commit 26daee4

Please sign in to comment.