Robolectric: Tests sometimes fail when gradle run with --parallel

Created on 8 Dec 2017  路  4Comments  路  Source: robolectric/robolectric

Description

I have a multimodule project where tests sometimes fail when run in gradle's parallel mode. When this happens, tests from the first executed submodule fail, but following submodules' tests pass.

The failing tests all show the following stacktrace:

java.lang.NoClassDefFoundError: android/content/pm/PackageManager$NameNotFoundException
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
    at java.lang.Class.getConstructor0(Class.java:3075)
    at java.lang.Class.getConstructor(Class.java:1825)
    at org.robolectric.RobolectricTestRunner.getHooksInterface(RobolectricTestRunner.java:461)
    at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:300)
        ...
Caused by: java.lang.ClassNotFoundException: couldn't load android.content.pm.PackageManager$NameNotFoundException
    at org.robolectric.internal.bytecode.SandboxClassLoader.getByteCode(SandboxClassLoader.java:195)
    at org.robolectric.internal.bytecode.SandboxClassLoader.maybeInstrumentClass(SandboxClassLoader.java:138)
    at org.robolectric.internal.bytecode.SandboxClassLoader.findClass(SandboxClassLoader.java:131)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 45 more
Caused by: java.util.zip.ZipException: invalid LOC header (bad signature)
    at java.util.zip.ZipFile.read(Native Method)
    at java.util.zip.ZipFile.access$1400(ZipFile.java:60)
    at java.util.zip.ZipFile$ZipFileInputStream.read(ZipFile.java:734)
    at java.util.zip.ZipFile$ZipFileInflaterInputStream.fill(ZipFile.java:434)
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:158)
    at java.io.FilterInputStream.read(FilterInputStream.java:133)
    at java.io.FilterInputStream.read(FilterInputStream.java:107)
    at org.robolectric.util.Util.copy(Util.java:21)
    at org.robolectric.util.Util.readBytes(Util.java:38)
    at org.robolectric.internal.bytecode.SandboxClassLoader.getByteCode(SandboxClassLoader.java:193)
    ... 49 more

It happens roughly 4 out of 10 runs.
When running gradle with -d option, it never fails. (So it is definitely a timing issue, as with -d the build takea a lot longer)

Robolectric & Android Version

build tools version = 26.0.2
compileSdkVersion = 24
robolectric = 3.5.1

Most helpful comment

Not really a concrete fix, but.. I've had success of resolving this issue by configuring Robolectric to not use runtime dependency resolution and download them before executing tests, like so:

configurations {
    roboJellybean
    roboKitkat
    roboLollipop
}

dependencies {
    // Add or remove any platform dependencies which you use
    roboJellybean "org.robolectric:android-all:4.1.2_r1-robolectric-r1"
    roboKitkat "org.robolectric:android-all:4.4_r1-robolectric-r2"
    roboLollipop "org.robolectric:android-all:5.0.2_r3-robolectric-r0"
}

def robolectricDependencies = "${rootProject.buildDir.path}/robolectric"

task fetchRobolectricDependencies(type: Copy) {
    from configurations.roboJellybean
    from configurations.roboKitkat
    from configurations.roboLollipop
    into robolectricDependencies
}

subprojects {
    afterEvaluate {
        if (project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.library")) {
            android {
                testOptions.unitTests.all {
                    systemProperty 'robolectric.offline', 'true'
                    systemProperty 'robolectric.dependency.dir', robolectricDependencies
                }
            }

            tasks.withType(Test) {
                it.dependsOn fetchRobolectricDependencies
            }
        }
    }
}

This way it could still run in parallel

All 4 comments

Any update on this?

Not really a concrete fix, but.. I've had success of resolving this issue by configuring Robolectric to not use runtime dependency resolution and download them before executing tests, like so:

configurations {
    roboJellybean
    roboKitkat
    roboLollipop
}

dependencies {
    // Add or remove any platform dependencies which you use
    roboJellybean "org.robolectric:android-all:4.1.2_r1-robolectric-r1"
    roboKitkat "org.robolectric:android-all:4.4_r1-robolectric-r2"
    roboLollipop "org.robolectric:android-all:5.0.2_r3-robolectric-r0"
}

def robolectricDependencies = "${rootProject.buildDir.path}/robolectric"

task fetchRobolectricDependencies(type: Copy) {
    from configurations.roboJellybean
    from configurations.roboKitkat
    from configurations.roboLollipop
    into robolectricDependencies
}

subprojects {
    afterEvaluate {
        if (project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.library")) {
            android {
                testOptions.unitTests.all {
                    systemProperty 'robolectric.offline', 'true'
                    systemProperty 'robolectric.dependency.dir', robolectricDependencies
                }
            }

            tasks.withType(Test) {
                it.dependsOn fetchRobolectricDependencies
            }
        }
    }
}

This way it could still run in parallel

Race condition while downloading android-all.jar I'm guessing鈥β爁irst thread (or process?) starts downloading via MavenDependencyResolver, which creates an empty or truncated file, which the second thread (or process?) uses prematurely.

Looks like a dupe of #2346.

Was this page helpful?
0 / 5 - 0 ratings