This chapter continues from where Defining Java modules left us.

Test coverage report using jacoco

In addition to compiling, running automated tests is one of the main tasks of build scripts.

But since we want to keep things declarative instead of imperative, we don't want to tell iwant to compile or run tests. Instead we wish for paths to targets that include the results of compiling and running tests.

The result of a compilation as a noun is easy, and many build solutions manage to keep the vocabulary declarative there. But how to avoid being imperative with test runs?

The result of a test run is basically a boolean: the tests either all passed or there was a failure. But we can do better than that: in addition to the success of the tests, we are also interested in the code coverage they give us.

So, with iwant, "running tests" means wishing for a coverage report.

Let's see how this is done. First we'll enable the needed plugin, jacoco, and refresh eclipse settings.

~/iwant-tutorial $ $EDITOR "as-iwant-tutorial-developer/i-have/wsdefdef/src/main/java/com/example/wsdefdef/IwantTutorialWorkspaceProvider.java"
package com.example.wsdefdef;
import net.sf.iwant.api.javamodules.JavaSrcModule;
import net.sf.iwant.api.wsdef.WorkspaceModuleContext;
import net.sf.iwant.api.wsdef.WorkspaceModuleProvider;
public class IwantTutorialWorkspaceProvider implements WorkspaceModuleProvider {
@Override
public JavaSrcModule workspaceModule(WorkspaceModuleContext ctx) {
return JavaSrcModule.with().name("iwant-tutorial-wsdef")
.locationUnderWsRoot("as-iwant-tutorial-developer/i-have/wsdef")
.mainJava("src/main/java").mainDeps(ctx.iwantApiModules())
.mainDeps(ctx.wsdefdefModule()).end();
.mainDeps(ctx.wsdefdefModule())
.mainDeps(ctx.iwantPlugin().jacoco().withDependencies()).end();
}
@Override
public String workspaceFactoryClassname() {
return "com.example.wsdef.IwanttutorialWorkspaceFactory";
}
}
~/iwant-tutorial $ as-iwant-tutorial-developer/with/bash/iwant/side-effect/eclipse-settings/effective
(0/1 S~ net.sf.iwant.api.javamodules.JavaClasses iwant-tutorial-wsdefdef-main-classes)
(0/1 D! net.sf.iwant.core.download.SvnExported iwant-plugin-jacoco-main-java)
svn exporting (may take a while) https://svn.code.sf.net/p/iwant/code/trunk/optional/iwant-plugin-jacoco/src/main/java@885
(0/1 D! net.sf.iwant.core.download.SvnExported iwant-plugin-ant-main-java)
svn exporting (may take a while) https://svn.code.sf.net/p/iwant/code/trunk/optional/iwant-plugin-ant/src/main/java@885
(0/1 D! net.sf.iwant.core.download.Downloaded ant-1.10.1.jar)
(0/1 D! net.sf.iwant.core.download.Downloaded ant-launcher-1.10.1.jar)
(0/1 D! net.sf.iwant.api.javamodules.JavaClasses iwant-plugin-ant)
(0/1 D! net.sf.iwant.core.download.Downloaded commons-io-1.3.2.jar)
(0/1 D! net.sf.iwant.api.javamodules.JavaClasses iwant-plugin-jacoco)
(0/1 D~ net.sf.iwant.api.javamodules.JavaClasses iwant-tutorial-wsdef-main-classes)
(0/1 D~ net.sf.iwant.api.core.Concatenated eclipse-settings.bin-refs)
(example-hello)
( .project)
( .classpath)
( .settings/org.eclipse.jdt.core.prefs)
( .settings/org.eclipse.jdt.ui.prefs)
(example-helloutil)
( .project)
( .classpath)
( .settings/org.eclipse.jdt.core.prefs)
( .settings/org.eclipse.jdt.ui.prefs)
(as-iwant-tutorial-developer/i-have/wsdef)
( .project)
( .classpath)
( .settings/org.eclipse.jdt.core.prefs)
( .settings/org.eclipse.jdt.ui.prefs)
(as-iwant-tutorial-developer/i-have/wsdefdef)
( .project)
( .classpath)
( .settings/org.eclipse.jdt.core.prefs)
( .settings/org.eclipse.jdt.ui.prefs)

Then we define the jacoco-report target.

~/iwant-tutorial $ $EDITOR "as-iwant-tutorial-developer/i-have/wsdef/src/main/java/com/example/wsdef/IwanttutorialWorkspace.java"
package com.example.wsdef;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import net.sf.iwant.api.core.Concatenated;
import net.sf.iwant.api.core.Concatenated.ConcatenatedBuilder;
import net.sf.iwant.api.core.HelloTarget;
import net.sf.iwant.api.javamodules.JavaBinModule;
import net.sf.iwant.api.javamodules.JavaSrcModule;
import net.sf.iwant.api.javamodules.JavaSrcModule.IwantSrcModuleSpex;
import net.sf.iwant.api.model.Path;
import net.sf.iwant.api.model.SideEffect;
import net.sf.iwant.api.model.Target;
import net.sf.iwant.api.wsdef.SideEffectDefinitionContext;
import net.sf.iwant.api.wsdef.TargetDefinitionContext;
import net.sf.iwant.api.wsdef.Workspace;
import net.sf.iwant.core.download.TestedIwantDependencies;
import net.sf.iwant.core.javamodules.JavaModules;
import net.sf.iwant.eclipsesettings.EclipseSettings;
import net.sf.iwant.plugin.jacoco.JacocoDistribution;
import net.sf.iwant.plugin.jacoco.JacocoTargetsOfJavaModules;
public class IwanttutorialWorkspace implements Workspace {
class ExampleModules extends JavaModules {
@Override
protected IwantSrcModuleSpex commonSettings(IwantSrcModuleSpex m) {
return super.commonSettings(m).testDeps(junit);
}
final JavaBinModule asmAll = binModule("org/ow2/asm", "asm-all",
"5.0.1");
final JavaBinModule hamcrestCore = binModule("org/hamcrest",
"hamcrest-core", "1.3");
final JavaBinModule junit = binModule("junit", "junit", "4.11",
hamcrestCore);
final JavaSrcModule helloUtil = srcModule("example-helloutil")
.noMainResources().end();
final JavaSrcModule hello = srcModule("example-hello").noMainResources()
.mainDeps(helloUtil).end();
}
private final ExampleModules modules = new ExampleModules();
@Override
public List<? extends Target> targets(TargetDefinitionContext ctx) {
return Arrays.asList(new HelloTarget("hello", "hello from iwant\n"),
classpathStringOfAll());
jacocoReport(), classpathStringOfAll());
}
private Target jacocoReport() {
return JacocoTargetsOfJavaModules.with()
.jacocoWithDeps(jacoco(), modules.asmAll.mainArtifact())
.antJars(TestedIwantDependencies.antJar(),
TestedIwantDependencies.antLauncherJar())
.modules(modules.allSrcModules()).end()
.jacocoReport("jacoco-report");
}
private static JacocoDistribution jacoco() {
return JacocoDistribution.newestTestedVersion();
}
private Target classpathStringOfAll() {
ConcatenatedBuilder cp = Concatenated.named("all-as-cp");
cp.string(".");
for (Path jar : JavaModules
.mainArtifactJarsOf(modules.allSrcModules())) {
cp.string(File.pathSeparator).nativePathTo(jar);
}
return cp.end();
}
@Override
public List<? extends SideEffect> sideEffects(
SideEffectDefinitionContext ctx) {
return Arrays.asList(EclipseSettings.with().name("eclipse-settings")
.modules(ctx.wsdefdefJavaModule(), ctx.wsdefJavaModule())
.modules(modules.allSrcModules()).end());
}
}

We list targets to refresh the wish scripts and then wish for the coverage report.

~/iwant-tutorial $ as-iwant-tutorial-developer/with/bash/iwant/list-of/targets
(0/1 S~ net.sf.iwant.api.javamodules.JavaClasses iwant-tutorial-wsdef-main-classes)
hello
jacoco-report
all-as-cp
~/iwant-tutorial $ as-iwant-tutorial-developer/with/bash/iwant/target/jacoco-report/as-path
(0/1 D! net.sf.iwant.core.download.Downloaded jacoco-0.7.2.201409121644.zip)
(0/1 D! net.sf.iwant.plugin.jacoco.JacocoDistribution jacoco-0.7.2.201409121644)
Expanding: /home/hacker/.net.sf.iwant/cached/UnmodifiableUrl/http%3A/%2Frepo1.maven.org/maven2/org/jacoco/jacoco/0.7.2.201409121644/jacoco-0.7.2.201409121644.zip into /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/target/jacoco-0.7.2.201409121644
(0/1 D! net.sf.iwant.core.download.Downloaded asm-all-5.0.1.jar)
(0/1 D~ net.sf.iwant.api.javamodules.JavaClasses example-hello-test-classes)
(0/1 D! net.sf.iwant.plugin.jacoco.JacocoInstrumentation example-hello-main-classes.jacoco-instr)
Buildfile: /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/temp/w-0/example-hello-main-classes.jacoco-instr.xml

example-hello-main-classes.jacoco-instr:
[jacoco:instrument] Instrumented 1 classes to /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/target/example-hello-main-classes.jacoco-instr

BUILD SUCCESSFUL
Total time: 2 seconds
(0/1 D! net.sf.iwant.plugin.jacoco.JacocoInstrumentation example-helloutil-main-classes.jacoco-instr)
Buildfile: /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/temp/w-0/example-helloutil-main-classes.jacoco-instr.xml

example-helloutil-main-classes.jacoco-instr:
[jacoco:instrument] Instrumented 1 classes to /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/target/example-helloutil-main-classes.jacoco-instr

BUILD SUCCESSFUL
Total time: 1 second
(0/1 D! net.sf.iwant.api.core.ClassNameList example-hello-test-class-names)
(0/1 D! net.sf.iwant.plugin.jacoco.JacocoCoverage example-hello.jacococoverage)
Buildfile: /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/temp/w-0/example-hello.jacococoverage.xml

example-hello.jacococoverage:
[java] JUnit version 4.11
[java] .
[java] Time: 0,376
[java]
[java] OK (1 test)
[java]

BUILD SUCCESSFUL
Total time: 1 second
(0/1 D! net.sf.iwant.api.javamodules.JavaClasses example-helloutil-test-classes)
(0/1 D! net.sf.iwant.api.core.ClassNameList example-helloutil-test-class-names)
(0/1 D! net.sf.iwant.plugin.jacoco.JacocoCoverage example-helloutil.jacococoverage)
Buildfile: /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/temp/w-0/example-helloutil.jacococoverage.xml

example-helloutil.jacococoverage:
[java] JUnit version 4.11
[java] .
[java] Time: 0,549
[java]
[java] OK (1 test)
[java]

BUILD SUCCESSFUL
Total time: 2 seconds
(0/1 D! net.sf.iwant.plugin.jacoco.JacocoReport jacoco-report)
Buildfile: /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/temp/w-0/jacoco-report.xml

jacoco-report:
[echo] Generating /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/target/jacoco-report
[jacoco:report] Loading execution data file /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/target/example-hello.jacococoverage
[jacoco:report] Loading execution data file /home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/target/example-helloutil.jacococoverage
[jacoco:report] Writing bundle 'jacoco-report' with 2 classes

BUILD SUCCESSFUL
Total time: 1 second
/home/hacker/iwant-tutorial/as-iwant-tutorial-developer/.i-cached/target/jacoco-report

Let's see what is in the report. Here we already benefit from our declarative wish: since we didn't tell iwant to run anything and we haven't touched anything, we can just make the same wish again without having to wait for another test run.

~/iwant-tutorial $ as-iwant-tutorial-developer/with/bash/iwant/target/jacoco-report/as-path | xargs -r ls
com.example.hello
com.example.helloutil
index.html
report.csv
report.xml

You are probably more interested in the html version, but here we'll take a look at the csv file.

~/iwant-tutorial $ cat as-iwant-tutorial-developer/.i-cached/target/jacoco-report/report.csv
GROUP,PACKAGE,CLASS,INSTRUCTION_MISSED,INSTRUCTION_COVERED,BRANCH_MISSED,BRANCH_COVERED,LINE_MISSED,LINE_COVERED,COMPLEXITY_MISSED,COMPLEXITY_COVERED,METHOD_MISSED,METHOD_COVERED
jacoco-report,com.example.hello,HelloMain,10,4,0,0,3,1,2,1,2,1
jacoco-report,com.example.helloutil,HelloUtil,3,11,0,0,1,1,1,1,1,1
Output asserted