android-test
android-test copied to clipboard
Running a test with ActivityScenario while test coverage is enabled throws a NoClassDefFoundError
Steps to Reproduce
Follow the steps here to get Jacoco coverage for tests
https://about.codecov.io/blog/code-coverage-for-android-development-using-kotlin-jacoco-github-actions-and-codecov/
Try to run this test
@RunWith(AndroidJUnit4::class)
class StringAdapterTest {
@Test
fun testStringAdapter() {
ActivityScenario.launch(
TestActivity::class.java
).onActivity { activity ->
}
}
}
Expected Results
No exceptions.
Actual Results
java.lang.NoClassDefFoundError: Failed resolution of: Lorg/jacoco/agent/rt/internal_3570298/Offline;
at androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity.$jacocoInit(Unknown Source:13)
at androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity.<clinit>(Unknown Source:0)
at java.lang.Class.newInstance(Native Method)
at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
at android.app.Instrumentation.newActivity(Instrumentation.java:1253)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3725)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4022)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Caused by: java.lang.ClassNotFoundException: Didn't find class "org.jacoco.agent.rt.internal_3570298.Offline" on path: DexPathList[[zip file "/data/app/~~h8MxzSzcV5J1qu2yJeU5Dg==/com.example.inventory_manager.test-OTl1KcxOXWG6CFw7svj1Pg==/base.apk"],nativeLibraryDirectories=[/data/app/~~h8MxzSzcV5J1qu2yJeU5Dg==/com.example.inventory_manager.test-OTl1KcxOXWG6CFw7svj1Pg==/lib/arm64, /system/lib64, /system/system_ext/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:207)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity.$jacocoInit(Unknown Source:13)
at androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity.<clinit>(Unknown Source:0)
at java.lang.Class.newInstance(Native Method)
at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
at android.app.Instrumentation.newActivity(Instrumentation.java:1253)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3725)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4022)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
AndroidX Test and Android OS Versions
AndroidX Test version 1.4.0 and Android OS Version 11
This might help https://scans.gradle.com/s/k7h3cqmbgvwkm
And yet, we use them?
mike@bistromath:~/work/AnkiDroid/Anki-Android-Upstream (main) % grep -r ActivityScenario AnkiDroid/src/
AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java:import androidx.test.core.app.ActivityScenario;
AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java: try (ActivityScenario<DeckPicker> scenario = ActivityScenario.launch(DeckPicker.class)) {
AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java: try (ActivityScenario<DeckPicker> scenario = ActivityScenario.launch(DeckPicker.class)) {
AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java: try (ActivityScenario<DeckPicker> scenario = ActivityScenario.launch(DeckPicker.class)) {
AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java: try (ActivityScenario<DeckPicker> scenario = ActivityScenario.launch(DeckPicker.class)) {
AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java: try (ActivityScenario<DeckPicker> scenario = ActivityScenario.launch(DeckPicker.class)) {
AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java: try (ActivityScenario<DeckPicker> scenario = ActivityScenario.launch(DeckPicker.class)) {
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt:import androidx.test.core.app.ActivityScenario
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt: private var mActivityScenario: ActivityScenario<DeckPicker>? = null
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt: mActivityScenario = ActivityScenario.launch(DeckPicker::class.java)
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt: val activityScenario: ActivityScenario<DeckPicker>? = mActivityScenario
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt: mActivityScenario!!.onActivity { activity: DeckPicker ->
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt: mActivityScenario!!.onActivity { activity: DeckPicker ->
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt: mActivityScenario!!.onActivity { activity: DeckPicker ->
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt: mActivityScenario!!.onActivity { activity: DeckPicker ->
AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt: mActivityScenario!!.onActivity { activity: DeckPicker? ->
AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt:import androidx.test.core.app.ActivityScenario
AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt: ActivityScenario.launch(NoteEditor::class.java).use { scenario ->
AnkiDroid/src/test/java/com/ichi2/anki/ReviewerTest.kt:import androidx.test.core.app.ActivityScenario
AnkiDroid/src/test/java/com/ichi2/anki/ReviewerTest.kt: ActivityScenario.launch(Reviewer::class.java).use { scenario -> scenario.onActivity { reviewer: Reviewer -> assertNull("Collection should have been null", reviewer.col) } }
AnkiDroid/src/test/java/com/ichi2/anki/ReviewerTest.kt: ActivityScenario.launch(Reviewer::class.java).use { scenario -> scenario.onActivity { reviewer: Reviewer -> assertNotNull("Collection should be non-null", reviewer.col) } }
AnkiDroid/src/test/java/com/ichi2/anki/ReviewerTest.kt: ActivityScenario.launch(Reviewer::class.java).use { scenario ->
AnkiDroid/src/test/java/com/ichi2/testutils/PreferenceUtils.kt:import androidx.test.core.app.ActivityScenario
AnkiDroid/src/test/java/com/ichi2/testutils/PreferenceUtils.kt: ActivityScenario.launch<Preferences>(i).use { scenario ->
AnkiDroid/src/androidTest/java/com/ichi2/anki/DeckPickerTest.java:import androidx.test.ext.junit.rules.ActivityScenarioRule;
AnkiDroid/src/androidTest/java/com/ichi2/anki/DeckPickerTest.java: public ActivityScenarioRule<DeckPicker> mActivityRule = new ActivityScenarioRule<>(DeckPicker.class);
AnkiDroid/src/androidTest/java/com/ichi2/anki/NoteEditorTest.java:import androidx.test.ext.junit.rules.ActivityScenarioRule;
AnkiDroid/src/androidTest/java/com/ichi2/anki/NoteEditorTest.java: public ActivityScenarioRule<NoteEditor> mActivityRule = new ActivityScenarioRule<>(getNoteEditorIntent());
AnkiDroid/src/androidTest/java/com/ichi2/anki/NoteEditorTest.java: at androidx.test.core.app.ActivityScenario.waitForActivityToBecomeAnyOf(ActivityScenario.java:301)
AnkiDroid/src/androidTest/java/com/ichi2/anki/NoteEditorTabOrderTest.java:import androidx.test.core.app.ActivityScenario;
AnkiDroid/src/androidTest/java/com/ichi2/anki/NoteEditorTabOrderTest.java: ActivityScenario<NoteEditor> scenario = mActivityRule.getScenario();
AnkiDroid/src/androidTest/java/com/ichi2/anki/NoteEditorTabOrderTest.java: protected void onActivity(ActivityScenario<NoteEditor> scenario, ActivityScenario.ActivityAction<NoteEditor> noteEditorActivityAction) throws Throwable {
mike@bistromath:~/work/AnkiDroid/Anki-Android-Upstream (main) %
Ah, so if I remove Jacoco related stuff, I still get the same error
I forced Jacoco version 0.8.7 and see all Jacoco related dependencies are 0.8.7 in buildEnvironment
We are currently on 0.8.8 with no issue We were on 0.8.7 with no issue Prior to that, no issue, we haven't had issues with this, git blame link: https://github.com/ankidroid/Anki-Android/blame/main/AnkiDroid/jacoco.gradle#L6
Perhaps you should specify the JDK in use? We use JDK11 for build + test execution.
compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }
Whatever version that is.
I cleaned up my dependencies
implementation "androidx.navigation:navigation-fragment-ktx:2.4.2"
implementation "androidx.navigation:navigation-ui-ktx:2.4.2"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.recyclerview:recyclerview-selection:1.1.0"
implementation 'com.google.android.material:material:1.6.1'
implementation 'com.google.dagger:hilt-android:2.42'
kapt 'com.google.dagger:hilt-compiler:2.42'
testImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
androidTestImplementation 'androidx.test:runner:1.4.0'
I am pretty sure it is related to ActivityScenario because I use AndroidJUnit4 on other tests and they work. Same with org.junit.Test and org.junit.runner.RunWith
So actually, you do not even need to follow the steps in the link I posted. You just need the dependencies and testCoverageEnabled set to true. This build scan is leaner than the one above and has the same issue https://scans.gradle.com/s/pz7sfzxbgxrow
I don't have any more time to assist on this issue, sorry. All I can say is we have an open repo, and everything works. You're free to inspect and compare.
Here is a minimal reproducible example https://github.com/aljohnston112/TestJacocoBug/tree/main
Looks like a bug with ActivityScenario or AppCompatActivity. I think there is some kind of conflict between 'androidx.test:core-ktx:1.4.0' and 'com.google.android.material:material:1.6.1'.
Replacing the Material dependency with 'androidx.appcompat:appcompat:1.4.1' does not help
Using an android.app.Activity does not work either
Just a blind stab: can it be that you need to put org.jacoco
into the first dex?
(https://developer.android.com/studio/build/multidex#multidexkeepproguard-property)
-keep class org.jacoco.** { *; }
and
debug {
testCoverageEnabled true
multiDexKeepProguard file('multidex-config.pro')
}
didn't help
Wow, so I needed debugImplementation 'androidx.test:core-ktx:1.4.0'
.
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
was not enough.
Definitely something to add here https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/test-setup unless it actually is a bug.
On another note, trying to debug with the test in the minimal reproducible example causes a NumberFormatException as the program is trying to parse "config_mainBuiltInDisplayCutout" as a number
Hmmm, a rebuild fixed that
Wow, so I needed debugImplementation 'androidx.test:core-ktx:1.4.0'. androidTestImplementation 'androidx.test:core-ktx:1.4.0' was not enough.
What a catch. Definitely solves the problem.
Wow, so I needed debugImplementation 'androidx.test:core-ktx:1.4.0'. androidTestImplementation 'androidx.test:core-ktx:1.4.0' was not enough.
What a catch. Definitely solves the problem.
I can confirm that this solved the issue for me too. I didn't even have this dependency before. When I did upgrade to AGP 7.2.2 and Gradle 7.4.2 instrumentation tests started failing. Also, removing testCoverageEnabled true
resolved tests crashing but I need that because of the Jacoco setup. The error I had was this one:
java.lang.AssertionError: Activity never becomes requested state "[CREATED, STARTED, RESUMED, DESTROYED]" (last lifecycle transition = "PRE_ON_CREATE")
at androidx.test.core.app.ActivityScenario.waitForActivityToBecomeAnyOf(ActivityScenario.java:338)