Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimum SDK < 24 and coreLibraryDesugaring causes NoSuchMethodError in ConcurrentHashMap #225

Closed
alexsullivan114 opened this issue Oct 7, 2020 · 14 comments
Assignees

Comments

@alexsullivan114
Copy link

alexsullivan114 commented Oct 7, 2020

First off, I want to thank the maintainers of this repo for doing the herculean work of getting JUnit 5 up and running on Android!

I'm running into an issue where I can run my instrumentation tests just fine within Android Studio, but if I try to run them from the command line I get two sets of errors.

First off, these are the errors I'm seeing:

First error:

module.myModule.myAndroidTest > initializationError[emulator-5554 - 8.1.0] FAILED 
        java.lang.NoSuchMethodError: No static method newKeySet()Lj$/util/concurrent/ConcurrentHashMap$KeySetView; in class Lj$/util/concurrent/ConcurrentHashMap; or its super classes (declaration of 'j$.util.concurrent.ConcurrentHashMap' appears in /data/app/module.ins.test-TnAh7dSYSDIdYT5oUAsyiQ==/base.apk!classes6.dex)
        at org.junit.platform.commons.logging.LoggerFactory.<clinit>(LoggerFactory.java:36)

Second error:

module.myModule.myPackage.myOtherAndroidTest > initializationError[emulator-5554 - 8.1.0] FAILED 
        java.lang.IllegalStateException: junit-platform-runner not found on runtime classpath of instrumentation tests; please review your androidTest dependencies or raise an issue.
        at de.mannodermaus.junit5.AndroidJUnit5Builder.runnerForClass(RunnerBuilder.kt:72)

The second error is repeated for each class in my androidTest folder.

Here's the build.gradle for the module I'm attempting to test:

apply plugin: 'com.android.library'
apply plugin: "de.mannodermaus.android-junit5"

android {
    compileSdkVersion ...

    defaultConfig {
        ...
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        testInstrumentationRunnerArgument "runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder"
    }

    ...

    compileOptions {
        coreLibraryDesugaringEnabled=true

        sourceCompatibility = "1.8"
        targetCompatibility = "1.8"
    }

    packagingOptions {
        exclude "META-INF/LICENSE*"
    }
}

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

    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'

    testImplementation "junit:junit:${rootProject.ext.junitVer}"
    testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.6.2"
    testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.6.2"

    androidTestImplementation "androidx.test:runner:1.3.0"
    androidTestImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2"

    androidTestImplementation "de.mannodermaus.junit5:android-test-core:1.2.0"
    androidTestRuntimeOnly "de.mannodermaus.junit5:android-test-runner:1.2.0"

    androidTestImplementation "org.mockito:mockito-core:3.3.3"
    testImplementation "org.mockito:mockito-core:3.3.3"
}

...

The errors I'm seeing are awfully close to the errors in this stack overflow post, but we're not using Kotlin so the proposed solution doesn't work for us (unless the issue is some library we're using is using Kotlin under the hood?)

Any help anyone could be provide would be much appreciated. Thank you!

EDIT: Tried to add Kotlin to the project so I could specify the jvmTarget in the kotlinOptions block and add a dependency on the jdk8 standard library as outlined in the linked StackOverflow post but alas no luck.

EDIT: It looks like updating the minSdk to >= 24 fixes the issue.

@alexsullivan114
Copy link
Author

I've reopened this issue primarily to ask if this is expected behavior. Is a minSdk >= 24 required for instrumentation tests?

@mannodermaus
Copy link
Owner

mannodermaus commented Oct 11, 2020

Thanks for bringing this up. The two test failure messages seem unrelated to each other, let's tackle them one by one.

The NoSuchMethodError going into j$/util/concurrent/ConcurrentHashMap$KeySetView makes me think that there's a problem with L8 and androidTest sources. You seem to be using core library desugaring - I vaguely remember reading somewhere that the desugared types don't currently work in instrumentation tests. Since the JUnit 5 libs fundamentally build on top of Java 8, it's possible that L8's rewrite accidentally repackages these libraries, causing the failure at runtime.

As for the second error, it's weird that you'd need the explicit minSdk here, since the runtime should automatically deactivate itself when running on an older device. (In fact, you'd techncally need API 26 to run JUnit 5 tests on device, 24 isn't enough.) I'm wondering if it's possible that the aforementioned L8 rewrite is at fault here after all. If it's not too much of a hassle, how does this reproduce in a project without coreLibraryDesugaringEnabled? Also, I don't know if this Gradle configuration is actually a thing, but I'm wondering if we'd have to explicitly define something like androidTestCoreLibraryDesugaring for the lib to be active for tests..?

@alexsullivan114
Copy link
Author

Ahhh I tried it while removing the coreLibraryDesugaringEnabled call and everything worked as expected. Interesting. I couldn't find any sort of androidTestCoreLibaryDesugaring method to employ. The technical details of this are a bit out of my wheelhouse - @mannodermaus do you feel like this is a bug within this project, or something to raise with Google?

@mannodermaus
Copy link
Owner

mannodermaus commented Oct 17, 2020

I'm assuming that L8's rewriting of core Java classes for maintaining Android compatibility clashes with the JUnit 5 stuff that uses the actual Java classes. By giving a higher minSdk, you effectively turn off the desugaring for classes that can be used anyway. The desugaring tool would need to rewrite the JUnit's JAR code from java.util... to j$.util as well in order to fix this, I suppose. Let me do some research and discovery here.

What I'd like to know is: If you use minSdk < 24 here, will you encounter the above runtime error on any device, no matter if it's newer or older than API 24? Or will only the old devices crash with it?

Edit: Re-read the question just now. You're saying that your tests execute fine from within AS, but fail from command line. What's the Gradle task you use to run from command line? Maybe the task graph is different between the two, and Android Studio has some internal knowledge of running the desugaring whereas the command line does not.

@mannodermaus mannodermaus self-assigned this Oct 17, 2020
@mannodermaus mannodermaus changed the title Struggling to run instrumentation tests from command line Minimum SDK < 24 and coreLibraryDesugaring causes NoSuchMethodError in ConcurrentHashMap Aug 6, 2021
@mannodermaus
Copy link
Owner

After more experimentation, I am convinced that this behavior is a bug inside the Android Gradle Plugin and/or L8 desugaring stack. I have raised an issue on the Google bug tracker for it and will lock down this ticket until there is some feedback from the Android team.

https://issuetracker.google.com/issues/195786468

@shawnthye
Copy link

After more experimentation, I am convinced that this behavior is a bug inside the Android Gradle Plugin and/or L8 desugaring stack. I have raised an issue on the Google bug tracker for it and will lock down this ticket until there is some feedback from the Android team.

https://issuetracker.google.com/issues/195786468

Having the same issue on Arctic Fox, AGP 7.0.3 with Min SDK 21 on module,

worked with id("com.android.application")

failed with id("com.android.library") ?

included the log here, in case you need this

java.lang.NoSuchMethodError: No static method newKeySet()Lj$/util/concurrent/ConcurrentHashMap$KeySetView; in class Lj$/util/concurrent/ConcurrentHashMap; or its super classes (declaration of 'j$.util.concurrent.ConcurrentHashMap' appears in /data/app/~~iyR0exDW6dOtiOLN7wTyow==/feature.playground.deviant.test-b8BabFAg4DsvcymQy-0Q-g==/base.apk!classes5.dex)
	at org.junit.platform.commons.logging.LoggerFactory.<clinit>(LoggerFactory.java:36)
	at org.junit.platform.commons.logging.LoggerFactory.getLogger(LoggerFactory.java:47)
	at org.junit.platform.launcher.core.ServiceLoaderRegistry.<clinit>(ServiceLoaderRegistry.java:27)
	at org.junit.platform.launcher.core.LauncherFactory.<clinit>(LauncherFactory.java:66)
	at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:109)
	at de.mannodermaus.junit5.internal.runners.AndroidJUnit5.<init>(AndroidJUnit5.kt:32)
	at de.mannodermaus.junit5.internal.runners.AndroidJUnit5.<init>(AndroidJUnit5.kt:27)
	at de.mannodermaus.junit5.internal.runners.JUnit5RunnerFactory.createJUnit5Runner$runner_release(JUnit5RunnerFactory.kt:16)
	at de.mannodermaus.junit5.AndroidJUnit5Builder.runnerForClass(AndroidJUnit5Builder.kt:71)
	at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
	at androidx.test.internal.runner.AndroidRunnerBuilder.runnerForClass(AndroidRunnerBuilder.java:147)
	at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
	at androidx.test.internal.runner.TestLoader.doCreateRunner(TestLoader.java:73)
	at androidx.test.internal.runner.TestLoader.getRunnersFor(TestLoader.java:105)
	at androidx.test.internal.runner.TestRequestBuilder.build(TestRequestBuilder.java:804)
	at androidx.test.runner.AndroidJUnitRunner.buildRequest(AndroidJUnitRunner.java:613)
	at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:411)
	at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2205)

@shawnthye
Copy link

However setting Minimum SDK to API 26 also resolve the issue

@mannodermaus
Copy link
Owner

Thanks @shawnthye! This does reflect the behavior that I've seen when the minimum SDK is below 26. The desugaring part of the Android Gradle Plugin doesn't kick in for this method if minSdk >= 26, since at that point the "real" API is available on devices, so no desugaring is required. Quite interesting that it worked for you when using the application plugin. Maybe the L8 bug is specific to libraries then..?

Unfortunately I haven't heard back from the ticket on the Google issue tracker above, causing this to remain at stalemate for the time being.

@shawnthye-zalora
Copy link

Thanks @shawnthye! This does reflect the behavior that I've seen when the minimum SDK is below 26. The desugaring part of the Android Gradle Plugin doesn't kick in for this method if minSdk >= 26, since at that point the "real" API is available on devices, so no desugaring is required. Quite interesting that it worked for you when using the application plugin. Maybe the L8 bug is specific to libraries then..?

Unfortunately I haven't heard back from the ticket on the Google issue tracker above, causing this to remain at stalemate for the time being.

yea @mannodermaus , seem like only for Library module

@alexsullivan114 error also written with some package name module.myModule.myAndroidTest

😄

@mattrob33
Copy link

Any progress on this? I encountered the same issue, and switching from JUnit5 to JUnit4 fixed it, without any other changes.

@mannodermaus
Copy link
Owner

Nothing to share, sorry. Feel free to star the ticket on the Google issue tracker (linked above) to give some visibility to it. It's not in the realm of possibility for the plugin to address this problem, unfortunately.

@mannodermaus
Copy link
Owner

I found this line in the release notes of the desugaring library 1.2.0:

Support for all methods on java.util.concurrent.ConcurrentHashMap.

Sounds promising. Note to self to check out this particular issue against version 1.2.0!

@kronstein
Copy link

JLYK, v1.2.0 fixes the issue.

Though, if after upgrade and running your tests you see another error: java.lang.ClassCastException: j$.util.stream.ReferencePipeline$Head cannot be cast to java.util.stream.Stream, then you need desugar lib v2.0.0 and AGP v7.4.0-rc03 (which comes with Android Studio 2022.1.1 Electric Eel). See: https://issuetracker.google.com/issues/243636261.

@mannodermaus
Copy link
Owner

Thanks for letting me know and the additional pointer, @kronstein! Kind of unfortunate that there doesn't seem to be a good way to backport that second fix to the desugar 1.x line, but at least it works on 2.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants