Gradle & Robolectric & Jacoco & Dagger

As a few have, I stumbled into this $$ jacoco bug. To be fair this is not so much a GradlePlugin bug as more Jacoco/Dagger.

Semantics aside, we wanted to know about our unit test coverage with Robolectric.

Implimentations vary, I based alot of my work off of this template.

Unlike the template I decided to embed Jacoco into my main project. (Maybe a jacoco.gradle file might be better, but thats trivial).

At time of writing we use:

  • Gradle 1.12
  • Android Gradle Plugin 0.11.+
  • Robolectric plugin 0.11.+

/build.gradle:

// ^^^ Other build stuff up here ^^^

apply plugin: "jacoco"

jacoco {  
    // I have only tried 0.7.x
    toolVersion = "0.7.1.201405082137"
}
// Define coverage source.
// If you have rs/aidl etc... add them here.
def coverageSourceDirs = [  
        'src/main/java',
        'src/gen'
]

// This differs per what flavors buildTypes etc. 
// But this example shows the 'testDebug'
// which is standard for Robolectric
task jacocoTestReport(type: JacocoReport, dependsOn: "testDebug") {  
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled = true // coveralls plugin depends on xml format report
        html.enabled = true
    }
    // class R is used, but usage will not be covered, so ignore this class from report
    // This differs per plugin version (0.10 -> 0.11) 
    // have very different fileTrees.
    // I have added rules to Ignore Dagger/Butterknife
    classDirectories = fileTree(
            dir: './build/intermediates/classes/debug',
            excludes: ['com/myapp/R*.class',
                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
            ])
    sourceDirectories = files(coverageSourceDirs)
    executionData = files('build/jacoco/testDebug.exec')
    // Bit hacky but fixes https://code.google.com/p/android/issues/detail?id=69174.
    // We iterate through the compiled .class tree and rename $$ to $. 
    doFirst {
        new File('myapp/build/intermediates/classes/').eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }
    afterEvaluate {
        // just clean up coveralls dashboard, following reports are not of interest
        testDebug.reports.junitXml.enabled = false
    }
}

Run: $ ./gradlew jacocoTestReport

Caveat:

I recommend you clean then assemble if you are going to build an app after testing.

The $$ -> $ shouldn't break anything, but it's better to be safe than sorry especialy as Dagger and Butterknife use partial reflection to look up some of the generated classes.

Thoughts:

By no means is this script 100% but its about as concise as I could get it for my use case. As the $$ bug has been around since 2011, no one seems to want to fix it. Until then, I can't see any detrimental effect of just renaming the files.

Also I don't care so much about Android Studio / Intelij intergration, we use CI and build stats.
Push & forgot, CI tests and warns.

Follow up

I will add to this when I get (http://coveralls.io)[http://coveralls.io] up and running.