1

we have a multi module project with some applications and libraries and use JaCoCo for coverage of unit tests and instrumented tests on android. Therefore we use the standard jacoco plugin for gradle together with android plugin for gradle. The JaCoCo target we use is createStandardDebugCoverageReport.

However, we see some behaviour we can neither explain nor change: On both modules the tests run and pass, but on the one module the coverage report is created and downloaded from the emulator. On the other module, we see the error message after the test passed:

Caused by: java.lang.IllegalStateException: JaCoCo agent not started.

on logcat and thus a file with zero bytes is created.

We tested it with have two similar applications with ExampleUnitTest and ExampleInstrumentedTest classes.

The whole JaCoCo configuration is done in the project's root gradle.

Project's Root Gradle:

import org.ajoberstar.grgit.Grgit

buildscript {
    ext {
        jacocoVersion = "0.8.1"
    }
    repositories {
        maven {
            url "our.artifactory.url"
            credentials {
                username System.getenv("ARTIFACTORY_LOCAL_USERNAME") ? System.getenv("ARTIFACTORY_LOCAL_USERNAME") : project.property("ARTIFACTORY_LOCAL_USERNAME")
                password System.getenv("ARTIFACTORY_LOCAL_PASSWORD") ? System.getenv("ARTIFACTORY_LOCAL_PASSWORD") : project.property("ARTIFACTORY_LOCAL_PASSWORD")
            }
        }
        // The used repositories need to be configured within artifactory because we use it as dependency cache.
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.0-alpha13'
        classpath 'com.google.gms:google-services:3.2.0'
        classpath 'io.fabric.tools:gradle:1.26.1'
        classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.7.5'
        classpath 'org.ajoberstar:grgit:2.2.1'
        classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2"

        classpath "org.jacoco:org.jacoco.core:${jacocoVersion}"
        classpath "org.jacoco:org.jacoco.report:${jacocoVersion}"
        classpath "org.jacoco:org.jacoco.agent:${jacocoVersion}"

        classpath "org.codehaus.groovy:groovy-all:2.4.15"
    }
}

plugins {
    id "org.sonarqube" version "2.6.2"
}

// This block encapsulates custom properties and makes them available to all
// modules in the project.
ext {
    versionCode = 110
    versionName = "0.17.3"

    currentBranchName = System.getenv("BRANCH")

    def gitDirectory = file(file('.').parentFile.absolutePath + File.separator + '.git')
    if (currentBranchName.equals("unknown") && gitDirectory.exists()) {
        git = Grgit.open(dir: gitDirectory.parentFile)
        currentBranchName = git.branch.current.name
    }

    artifactoryUser = System.getenv("ARTIFACTORY_LOCAL_USERNAME")
    artifactoryPassword = System.getenv("ARTIFACTORY_LOCAL_PASSWORD")

    minSdkVersion = 23
    compileSdkVersion = 28
    targetSdkVersion = 28
    supportLibVersion = "28.0.0"
    playServicesVersion = "16.2.0"
    jacocoVersion = "0.8.1"
}

allprojects {
    apply plugin: "com.jfrog.artifactory"
    apply plugin: 'maven-publish'

    repositories {
        maven {
            url "our.artifactory.url"
            credentials {
                username System.getenv("ARTIFACTORY_LOCAL_USERNAME") ? System.getenv("ARTIFACTORY_LOCAL_USERNAME") : project.property("ARTIFACTORY_LOCAL_USERNAME")
                password System.getenv("ARTIFACTORY_LOCAL_PASSWORD") ? System.getenv("ARTIFACTORY_LOCAL_PASSWORD") : project.property("ARTIFACTORY_LOCAL_PASSWORD")
            }
        }
        // The used repositories need to be configured within artifactory because we use it as dependency cache.
    }

// This is the only place of any JaCoCo configuration
    apply plugin: 'jacoco'

    tasks.withType(Test) {
        jacoco.includeNoLocationClasses = true
    }
}

The following is a wear application, the working sample:

apply plugin: 'com.android.application'

configurations {
    demoDebugCompile
    demoReleaseCompile
    standardDebugCompile
    standardReleaseCompile
}

android {
    signingConfigs {
        ourSigningConfig {
            keyAlias getEnvProperty("SIGNING_KEY_ALIAS")
            keyPassword getEnvProperty("SIGNING_KEY_PASSWORD")
            storeFile file(getEnvProperty("SIGNING_STORE_FILE"))
            storePassword getEnvProperty("SIGNING_STORE_PASSWORD")
        }
    }

    compileSdkVersion rootProject.ext.compileSdkVersion

    defaultConfig {
        applicationId "our.working.applicationid"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
        multiDexEnabled true
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            debuggable false
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.ourSigningConfig
        }

        debug {
            debuggable true
            minifyEnabled false
            applicationIdSuffix ".debug"
            versionNameSuffix " Debug"
            testCoverageEnabled true
        }
    }

    flavorDimensions "mode"

    productFlavors {
        demo {
            dimension "mode"
            applicationIdSuffix ".demo"
            versionNameSuffix " Demo"
            buildConfigField "boolean", "DEMO", "true"
        }

        standard {
            dimension "mode"
            buildConfigField "boolean", "DEMO", "false"
        }
    }

    testOptions {
        unitTests {
            includeAndroidResources = true
            returnDefaultValues = false
        }

        unitTests.all {
            jvmArgs '-noverify'
            // https://stackoverflow.com/questions/32315978/jvm-options-in-android-when-run-gradlew-test/37593189#37593189
        }
    }

    lintOptions {
        abortOnError false
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    // External libraries
    implementation fileTree(include: ['*.jar'], dir: 'libs')

    // Support library
    implementation 'com.google.android.support:wearable:2.1.0'
    compileOnly 'com.google.android.wearable:wearable:2.1.0'

    // Google Play services
    implementation "com.google.android.gms:play-services-wearable:15.0.1" // TODO: use ${rootProject.ext.supportLibVersion}

    // Authenticator
    implementation project(':authenticator')

    // Testing
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.robolectric:robolectric:4.0-beta-1'

    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        def requested = details.requested
        if (requested.group == 'com.android.support') {
            if (!requested.name.startsWith("multidex")) {
                details.useVersion "${rootProject.ext.supportLibVersion}"
            }
        }
    }
}

artifactoryPublish.skip = true

The following is a dummy application. This one gives us the agent not started exception. We compared the module gradle files and enhanced this one with the dependencies of the working gradle file.

apply plugin: 'com.android.application'

configurations {
    demoDebugCompile
    demoReleaseCompile
    standardDebugCompile
    standardReleaseCompile
}

android {
    compileSdkVersion rootProject.ext.compileSdkVersion

    defaultConfig {
        applicationId "our.nonworking.applicationid"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
        multiDexEnabled true
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }

    buildTypes {
        release {
            debuggable false
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            consumerProguardFiles 'proguard-rules.pro'
        }
        debug {
            debuggable true
            minifyEnabled false
            applicationIdSuffix ".debug"
            versionNameSuffix " Debug"
            testCoverageEnabled true
        }
    }

    flavorDimensions "mode"
    productFlavors {
        demo {
            dimension "mode"
            applicationIdSuffix ".demo"
            versionNameSuffix " Demo"
            buildConfigField "boolean", "DEMO", "true"
        }
        standard {
            dimension "mode"
            buildConfigField "boolean", "DEMO", "false"
        }
    }

    testOptions {
        unitTests {
            includeAndroidResources = true
            returnDefaultValues = false
        }
        unitTests.all {
            jvmArgs '-noverify'
            // https://stackoverflow.com/questions/32315978/jvm-options-in-android-when-run-gradlew-test/37593189#37593189
        }
    }

    lintOptions {
        abortOnError false
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')

    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    // Support library
    implementation 'com.google.android.support:wearable:2.1.0'
    compileOnly 'com.google.android.wearable:wearable:2.1.0'

    // Google Play services
    implementation "com.google.android.gms:play-services-wearable:15.0.1" // TODO: use ${rootProject.ext.supportLibVersion}

    // Authenticator
    implementation project(':authenticator')

    // Testing
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.robolectric:robolectric:4.0-beta-1'

    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        def requested = details.requested
        if (requested.group == 'com.android.support') {
            if (!requested.name.startsWith("multidex")) {
                details.useVersion "${rootProject.ext.supportLibVersion}"
            }
        }
    }
}

artifactoryPublish.skip = true

We are out of ideas now. What could have an influence on that? We see the same behaviour on some libs in our project. The emulator tested on is Pixel XL API 28. Do you have some suggestion, advice or hint?

Thanks in advance.

Uwinator
  • 141
  • 1
  • 9

2 Answers2

0

Starting with plugin 3.0.0, now you cannot configure Jacoco using the android block DSL, but instead jacoco version must be define in the classpath dependencies, alongside the Android plugin. So add the following:

buildscript {
  repositories {
    google()
    jcenter()
  }
  dependencies {
    // Android plugin 
    classpath 'com.android.tools.build:gradle:3.0.1'
    //Jacoco version
    classpath 'org.jacoco:org.jacoco.core:0.8.1'
  }
}

Hope this helps

  • Hey, thanks for the answer. Unfortunately, as you can see in my project root gradle file, I already have the android plugin and the jacoco core along with some other dependencies in the classpath. – Uwinator Nov 12 '18 at 09:16
0

I found a solution, which I unfortunately don't understand completely:

I unified the test config by extracting it into an extra gradle file, so that there should be no difference between the modules gradle config regarding tests.

That did not help, but improved my architecture however.

If I add instrumented tests to the modules that have these problems, it just works and the coverage reports are generated for all modules. The thing I don't understand is, that all these modules have an ExampleInstrumentedTest that passes. However, two modules do have just the ExampleInstrumentedTest and it generates the coverage report. They are both android applications. However, I had the problem of not generating a coverage report with another (very simple) application too. That's why I did not try to add other instrumented tests to the failing modules in the first place.

So, it works for me now. But if someone can give a hint why that works and using the ExampleInstrumentedTest did not work, it would be very much appreciated.

Uwinator
  • 141
  • 1
  • 9