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
Expand Up @@ -26,12 +26,15 @@
<jacoco.destFile>${project.build.directory}/coverage.exec</jacoco.destFile> <jacoco.destFile>${project.build.directory}/coverage.exec</jacoco.destFile>
<jacoco.append>false</jacoco.append> <jacoco.append>false</jacoco.append>
<jacoco.exclClassLoaders>sun.reflect.DelegatingClassLoader:MyClassLoader</jacoco.exclClassLoaders> <jacoco.exclClassLoaders>sun.reflect.DelegatingClassLoader:MyClassLoader</jacoco.exclClassLoaders>
<jacoco.inclBootstrapClasses>true</jacoco.inclBootstrapClasses>
<jacoco.inclNoLocationClasses>true</jacoco.inclNoLocationClasses>
<jacoco.sessionId>session</jacoco.sessionId> <jacoco.sessionId>session</jacoco.sessionId>
<jacoco.dumpOnExit>true</jacoco.dumpOnExit> <jacoco.dumpOnExit>true</jacoco.dumpOnExit>
<jacoco.output>file</jacoco.output> <jacoco.output>file</jacoco.output>
<jacoco.address>localhost</jacoco.address> <jacoco.address>localhost</jacoco.address>
<jacoco.port>9999</jacoco.port> <jacoco.port>9999</jacoco.port>
<jacoco.classDumpDir>${project.build.directory}/classdumps</jacoco.classDumpDir> <jacoco.classDumpDir>${project.build.directory}/classdumps</jacoco.classDumpDir>
<jacoco.jmx>true</jacoco.jmx>
</properties> </properties>


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


//backslashes will be escaped //backslashes will be escaped
agentOptions = agentOptions.replace("\\","\\\\"); agentOptions = agentOptions.replace("\\","\\\\");
Expand Down
10 changes: 10 additions & 0 deletions jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java
Expand Up @@ -80,6 +80,12 @@ public abstract class AbstractAgentMojo extends AbstractJacocoMojo {
* @parameter property="jacoco.inclBootstrapClasses" * @parameter property="jacoco.inclBootstrapClasses"
*/ */
Boolean 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 * A session identifier that is written with the execution data. Without
* this parameter a random identifier is created by the agent. * this parameter a random identifier is created by the agent.
Expand Down Expand Up @@ -190,6 +196,10 @@ AgentOptions createAgentOptions() {
agentOptions.setInclBootstrapClasses(inclBootstrapClasses agentOptions.setInclBootstrapClasses(inclBootstrapClasses
.booleanValue()); .booleanValue());
} }
if (inclNoLocationClasses != null) {
agentOptions.setInclNoLocationClasses(inclNoLocationClasses
.booleanValue());
}
if (sessionId != null) { if (sessionId != null) {
agentOptions.setSessionId(sessionId); agentOptions.setSessionId(sessionId);
} }
Expand Down
Expand Up @@ -62,68 +62,42 @@ public void teardown() {
recorder.assertNoException(); 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 @Test
public void testFilterAgentClass() { public void testFilterAgentClass() {
CoverageTransformer t = createTransformer(); CoverageTransformer t = createTransformer();
assertFalse(t.filter(classLoader, assertFalse(t.filter(classLoader,
"org/jacoco/agent/rt/internal/somepkg/SomeClass")); "org/jacoco/agent/rt/internal/somepkg/SomeClass",
protectionDomain));
} }


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


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


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


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


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


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


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


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


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


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


@Test @Test
public void testFilterExcludedClassNegative() { public void testFilterExcludedClassNegative() {
options.setExcludes("*Test"); options.setExcludes("*Test");
CoverageTransformer t = createTransformer(); 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 @Test
Expand Down
Expand Up @@ -45,7 +45,9 @@ public class CoverageTransformer implements ClassFileTransformer {


private final ClassFileDumper classFileDumper; private final ClassFileDumper classFileDumper;


private final boolean includeBootstrapClasses; private final boolean inclBootstrapClasses;

private final boolean inclNoLocationClasses;


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


public byte[] transform(final ClassLoader loader, final String classname, 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; return null;
} }


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

if (!filter(loader, classname)) {
return null; 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. * Checks whether this class should be instrumented.
* *
* @param loader * @param loader
* loader for the class * loader for the class
* @param classname * @param classname
* VM name of the class to check * VM name of the class to check
* @param protectionDomain
* protection domain for the class
* @return <code>true</code> if the class should be instrumented * @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 (loader == null) {
if (!includeBootstrapClasses) { if (!inclBootstrapClasses) {
return false; return false;
} }
} else { } else {
if (!inclNoLocationClasses && !hasSourceLocation(protectionDomain)) {
return false;
}
if (exclClassloader.matches(loader.getClass().getName())) { if (exclClassloader.matches(loader.getClass().getName())) {
return false; return false;
} }
Expand All @@ -147,6 +132,25 @@ boolean filter(final ClassLoader loader, final String classname) {
!excludes.matches(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) { private static String toVMName(final String srcName) {
return srcName.replace('.', '/'); return srcName.replace('.', '/');
} }
Expand Down
10 changes: 7 additions & 3 deletions org.jacoco.ant.test/src/org/jacoco/ant/AgentTaskTest.xml
Expand Up @@ -17,9 +17,10 @@


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

0 comments on commit 26daee4

Please sign in to comment.