miranda

add emma code coverage reporting to your gradle build

By breskeby | April 15, 2010

I’m actually porting an ant based build to gradle. The (deprecated) ant build contains different reporting tools like pmd, checkstyle, findbugs and much more. Since a colleague of mine just asks for code coverage statistics on our build, this post describes how to add EMMA support to your gradle build. EMMA is a free code coverage tool for java. If you havn’t heard about it have a look at http://emma.sourceforge.net/. It is shipped with custom ant tasks. And as we know, gradle works fine with ant. So,
How complicated can it be to get EMMA working with my gradle build?

Let’s have a look at a very simple java project gradle build file:

1
2
3
4
5
6
7
8
9
apply plugin:'java'

repositories{
    mavenCentral()
}

dependencies{
  testCompile "junit:junit:4.7"
}

This is all you need to get all java sources in src/main/java compiled, and run all junit tests you have stored at src/test/java.
After running this build by executing “gradle test
You can take a look at your test results at build/reports/test-results. But besides the test results we want to know which code we covered with our tests and which we don’t. There are different code coverage tools for java available. Since years I’m happy with EMMA for two reasons:

  1. the generated reports contain enough information for me
  2. there is a plugin available for the hudson ci server

What must be done to get EMMA code coverage work for your test task? EMMA in generell has two modi:

  • on-the-fly instrumentation mode
  • offline class instrumentation

on-the-fly instrumention can be used to add emma support on demand for every java application. for further information have a look at the EMMA reference.

To get EMMA working with our junit tests we use offline class instrumentation. This means, that EMMA instructs the compiled classes with emma specific information.

We need resolve the additional emma artifacts. luckily like junit, they are available at the central maven repo. To manage these additional artifacts, we add a custom configuration called emma and add the emma core and the emma ant modules to this configuration:

1
2
3
4
5
6
7
8
9
10
configurations{
    emma
}

dependencies{
  emma "emma:emma:2.0.5312"
  emma "emma:emma_ant:2.0.5312"

  testCompile "junit:junit:4.7"
}

Now we need to configure our test task to do byte code instrumentation on our compiled classes just before running the tests. we use the doFirst{} closure for that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
test{
   jvmArgs "-Demma.coverage.out.file=build/tmp/emma/metadata.emma", "-Demma.coverage.out.merge=true"

   doFirst{
      ant.taskdef( resource:"emma_ant.properties", classpath: configurations.emma.asPath)
        ant.path(id:"run.classpath"){
            pathelement(location:sourceSets.main.classesDir.absolutePath )
        }
        ant.emma(verbosity:'info'){
            instr(merge:"true", destdir:'build/tmp/emma/instr', instrpathref:"run.classpath", metadatafile:'build/tmp/emma/metadata.emma'){
                instrpath{
                    fileset(dir:sourceSets.main.classesDir.absolutePath, includes:"*.class")
                }
            }
        }
         setClasspath(files("$buildDir/tmp/emma/instr") + configurations.emma +  getClasspath())
      }
}

We do four things here:

  1. add EMMA related JVM args to our tests
  2. define the custom EMMA ant tasks
  3. instruct our compiled classes and store them at $buildDir/tmp/emma/instr
  4. update the test classpath with the instructed classes and the emma libs

Running your build script now via gradle test should now look similar to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rene-groschkes-macbook-pro:sample Rene$ gradle test
:compileJava
:processResources
:classes
:compileTestJava
:processTestResources
:testClasses
:test
processing instrumentation path ...
instrumentation path processed in 235 ms
[1 class(es) instrumented, 0 resource(s) copied]
metadata merged into [/Users/Rene/workspaces/gradle/github/gradleplugins/emmaPlugin/sample/build/tmp/emma/metadata.emma] {in 2 ms}

BUILD SUCCESSFUL

Total time: 7.709 secs

Now EMMA works with our junit tests. What we’re missing are the generated code coverage reports. The report should be generated directly after the tests are done. We add a doLast{} closure to do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
test{
   jvmArgs "-Demma.coverage.out.file=build/tmp/emma/metadata.emma", "-Demma.coverage.out.merge=true"

   doFirst{
      ant.taskdef( resource:"emma_ant.properties", classpath: configurations.emma.asPath)
        ant.path(id:"run.classpath"){
            pathelement(location:sourceSets.main.classesDir.absolutePath )
        }
        ant.emma(verbosity:'info'){
            instr(merge:"true", destdir:'build/tmp/emma/instr', instrpathref:"run.classpath", metadatafile:'build/tmp/emma/metadata.emma'){
                instrpath{
                    fileset(dir:sourceSets.main.classesDir.absolutePath, includes:"*.class")
                }
            }
        }
         setClasspath(files("$buildDir/tmp/emma/instr") + configurations.emma +  getClasspath())
      }

      doLast{
        ant.emma(enabled:"true"){
            report(sourcepath:"src/main/java"){
                fileset(dir:"build/tmp/emma"){
                    include(name:"*.emma")
                }
                txt(outfile:"build/reports/emma/coverage.txt")
                html(outfile:"build/reports/emma/coverage.html")
                xml(outfile:"build/reports/emma/coverage.xml")
            }
        }
    }
}

We create three types (txt, html, xml) of reports here. Running your build script now should result in output like that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rene-groschkes-macbook-pro:sample Rene$ gradle test
:compileJava
:processResources
:classes
:compileTestJava
:processTestResources
:testClasses
:test
processing instrumentation path ...
instrumentation path processed in 298 ms
[1 class(es) instrumented, 0 resource(s) copied]
metadata merged into [/Users/Rene/workspaces/gradle/github/gradleplugins/emmaPlugin/sample/build/tmp/emma/metadata.emma] {in 2 ms}
processing input files ...
1 file(s) read and merged in 3 ms
writing [txt] report to [/Users/Rene/workspaces/gradle/github/gradleplugins/emmaPlugin/sample/build/reports/emma/coverage.txt] ...
writing [html] report to [/Users/Rene/workspaces/gradle/github/gradleplugins/emmaPlugin/sample/build/reports/emma/coverage.html] ...
writing [xml] report to [/Users/Rene/workspaces/gradle/github/gradleplugins/emmaPlugin/sample/build/reports/emma/coverage.xml] ...

BUILD SUCCESSFUL

Total time: 8.221 secs

That wasn’t that complicated, was it? I shared a emma gradle plugin with a sample project on github.
This reduces our build script to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
apply plugin:'java'
apply from:'http://github.com/breskeby/gradleplugins/raw/master/emmaPlugin/emma.gradle'

repositories{
    mavenCentral()
}

dependencies{
  emma "emma:emma:2.0.5312"
  emma "emma:emma_ant:2.0.5312"

  testCompile "junit:junit:4.7"
}

Have fun with that. comments appreciated as always!

regards,
René

13 Responses to “add emma code coverage reporting to your gradle build”

  1. Hamlet D'Arcy Says:
    April 16th, 2010 at 09:02

    Great work.

    If I use your plugin directly, then my build requires an Internet connection and requires GitHub to be available, correct?

    There needs to be a Gradle hosted web page where plugins like these are listed. I cannot find them when I need them and do not know what exists out there. Would love to see a 3rd party plugins page added.

  2. Administrator Says:
    April 16th, 2010 at 17:16

    Hi Hamlet,
    I know the discussion of the distributed uncommented gradle plugins. There is a discussion on the gradle mailing list and at gradle jira going on about that topic. Hans is working on a gradle plugin portal like grails has (with some differents). At the moment the only place to share custom plugins is a gradle wiki page at http://gradle.codehaus.org/Plugins. I havn’t add this one, because there are still some issues with it i like to solve before (at the moment hard coded src path; not tested in multiproject environment.

  3. asaf Says:
    June 25th, 2010 at 21:02

    just wanted to say thanks, this works great

  4. René Says:
    June 25th, 2010 at 22:05

    thx

  5. Tom Says:
    October 13th, 2010 at 17:24

    Thank you for this posting. I am now able to generate emma coverage reports in my gradle project.

    Does anyone know if it’s possible to upload an emma coverage file to a Sonar server with gradle?

    Thanks,

  6. René Says:
    October 13th, 2010 at 17:42

    I’m not that familar with sonar repository server, but I think this should work. Artifactory supports any file type and I guess sonar also does. Let’s ship the discussion to the gradle mailing list (user@gradle.codehaus.org).

    regards,
    René

  7. Roger Says:
    November 5th, 2010 at 02:03

    Any conclusions on how well this works out in a multiproject build :) ?

  8. breskeby Says:
    November 5th, 2010 at 07:24

    well i use it in single and multiprojects in my company and it works fine.

  9. Kevin Says:
    January 20th, 2011 at 08:47

    Is there a way to exclude classes from coverage. I’ve tried several different ways to do it. Like:

    ant.emma(filter: ‘-*Model.class’)

    and

    ant.emma(…) {

    filter {
    excludes: ‘*Model.class’
    }
    }
    and

    ant.emma(…) {

    filter {
    excludes = ‘*Model.class’
    }
    }

    All of these run but none change the coverage class # (it’s always: 1103 class(es) instrumented, 0 resource(s) copied)

  10. breskeby Says:
    January 20th, 2011 at 23:13

    Hi Kevin,
    I’ve tested the exclude/include options for emma code coverage this evening. I ran into the same issues you did. I have to admit, that I didn’t right understand why it doesn’t work. I keep you informed when I have a solution.

    regards,
    René

  11. Kevin Says:
    March 2nd, 2011 at 19:28

    I’ve found the filter solution and it’s pretty straightforward. The change occurs on line 50 of the emma.gradle file on github. I’ve hardcoded the filter string here:

    the relevant piece is instr(filter: ‘-search.*’, …) where -search.* is the path to the classes that need to be filtered

    ——-

    ant.emma(verbosity:”${emmaConvention.verbosityLevel}”){
    instr(filter: ‘-search.*’, merge:”true”, destdir:”${emmaConvention.instrDir}”, instrpathref:”run.classpath”, metadatafile:”${emmaConvention.metaDataFilePath}”){

  12. Mark Says:
    May 13th, 2011 at 21:35

    Very nice!

    I did notice one small problem, though. I’m using maven conventions for the locations of my source code, and it doesn’t seem to be able to find the source files. I see this at the bottom of the generated HTML reports:

    source file ‘my/package/HelloWorld.java’ not found in sourcepath

    Thanks!

  13. Moustapha Says:
    July 13th, 2011 at 14:25

    It’s perfect! thanks

Comments