Java 7 Code Coverage with Gradle and Jacoco

September 13, 2012 - 3 minute read -
code java automation groovy

Thanks

Steven Dicks' post on Jacoco and Gradle is a great start to integrating Jacoco and Gradle, this is a small iteration on top of that work.

Java 7 Code Coverage

The state of Code Coverage took a serious turn for the worst when Java 7 came out. The byte-code changes in Java 7 effectively made Emma and Cobertura defunct. They will not work with Java 7 constructs. Fortunately there is a new player in town called JaCoCo (for Java Code Coverage). JaCoCo is the successor to Emma which is being built on the knowledge gained over the years by the Eclipse and Emma teams on how best to do code coverage. And works with Java 7 out-of-the-box.

The advantage of using established tools is that they generally are well supported across your toolchain. JaCoCo is fairly new and so support in Gradle isn't so smooth. Fortunately Steven's post got me started down the right path. The one thing that I wanted to improve right away was to use transitive dependency declarations as opposed to having local jar files in my source repository. JaCoCo is now available in the Maven repos so we can do that. One thing to note is that the default files build in the Maven repo are Eclipse plugins, so we need to reference the "runtime" classifier in our dependency

The Gradle Script

configurations {
    codeCoverage
    codeCoverageAnt
}
dependencies {
    codeCoverage 'org.jacoco:org.jacoco.agent:0.5.10.201208310627:runtime@jar'
    codeCoverageAnt 'org.jacoco:org.jacoco.ant:0.5.10.201208310627'
}
test {
    systemProperties = System.properties
    jvmArgs "-javaagent:${configurations.codeCoverage.asPath}=destfile=${project.buildDir.path}/coverage-results/jacoco.exec,sessionid=HSServ,append=false",
            '-Djacoco=true',
            '-Xms128m',
            '-Xmx512m',
            '-XX:MaxPermSize=128m'
}
task generateCoverageReport << {
    ant {
        taskdef(name:'jacocoreport', classname: 'org.jacoco.ant.ReportTask', classpath: configurations.codeCoverageAnt.asPath)
          mkdir dir: "build/reports/coverage"
          jacocoreport {
            executiondata {
                fileset(dir: "build/coverage-results") {
                    include name: 'jacoco.exec'
                }
            }
            structure(name: project.name) {
                classfiles {
                    fileset(dir: "build/classes/main") {
                        exclude name: 'org/ifxforum/**/*'
                        exclude name: 'org/gca/euronet/generated**/*'
                    }
                }
                sourcefiles(encoding: 'CP1252') {
                    fileset dir: "src/main/java"
                }
            }
              xml  destfile: "build/reports/coverage/jacoco.xml"
            html destdir:  "build/reports/coverage"
        }
    }
}

A Few Details

The magic is in the jvmArgs of the test block. JaCoCo is run as a Java Agent which uses the runtime instrumentation feature added in Java 6 to be able to inspect the running code. Extra arguments can be added to JaCoCo there including things like excludes to exclude specific classes from coverage. The available parameters are the same as the maven JaCoCo parameters.

The generateCoverageReport task converts the jacoco.exec binary into html files for human consumption. If you're just integrating with a CI tool, like Jenkins, then you probably don't need this, but it's handy for local use and to dig into the details of what's covered.

Loose Ends

One problem that I ran into was referencing project paths like the project.buildDir from within an Ant task. Hopefully someone will come along and let me know how that's done.