android-test icon indicating copy to clipboard operation
android-test copied to clipboard

Running a test with ActivityScenario while test coverage is enabled throws a NoClassDefFoundError

Open aljohnston112 opened this issue 2 years ago • 23 comments

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

aljohnston112 avatar Jun 02 '22 02:06 aljohnston112

This might help https://scans.gradle.com/s/k7h3cqmbgvwkm

aljohnston112 avatar Jun 02 '22 20:06 aljohnston112

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) % 

mikehardy avatar Jun 02 '22 21:06 mikehardy

Ah, so if I remove Jacoco related stuff, I still get the same error

aljohnston112 avatar Jun 02 '22 22:06 aljohnston112

I forced Jacoco version 0.8.7 and see all Jacoco related dependencies are 0.8.7 in buildEnvironment

aljohnston112 avatar Jun 02 '22 22:06 aljohnston112

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

mikehardy avatar Jun 02 '22 22:06 mikehardy

Perhaps you should specify the JDK in use? We use JDK11 for build + test execution.

mikehardy avatar Jun 02 '22 22:06 mikehardy

compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }

Whatever version that is.

aljohnston112 avatar Jun 02 '22 23:06 aljohnston112

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'

aljohnston112 avatar Jun 02 '22 23:06 aljohnston112

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

aljohnston112 avatar Jun 02 '22 23:06 aljohnston112

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

aljohnston112 avatar Jun 02 '22 23:06 aljohnston112

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.

mikehardy avatar Jun 02 '22 23:06 mikehardy

Here is a minimal reproducible example https://github.com/aljohnston112/TestJacocoBug/tree/main

aljohnston112 avatar Jun 03 '22 13:06 aljohnston112

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'.

aljohnston112 avatar Jun 03 '22 13:06 aljohnston112

Replacing the Material dependency with 'androidx.appcompat:appcompat:1.4.1' does not help

aljohnston112 avatar Jun 03 '22 13:06 aljohnston112

Using an android.app.Activity does not work either

aljohnston112 avatar Jun 03 '22 13:06 aljohnston112

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)

TWiStErRob avatar Jun 03 '22 20:06 TWiStErRob

 -keep class org.jacoco.** { *; }

and

        debug {
            testCoverageEnabled true
            multiDexKeepProguard file('multidex-config.pro')
        }

didn't help

aljohnston112 avatar Jun 03 '22 21:06 aljohnston112

Wow, so I needed debugImplementation 'androidx.test:core-ktx:1.4.0'. androidTestImplementation 'androidx.test:core-ktx:1.4.0' was not enough.

aljohnston112 avatar Jun 03 '22 22:06 aljohnston112

Definitely something to add here https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/test-setup unless it actually is a bug.

aljohnston112 avatar Jun 03 '22 22:06 aljohnston112

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

aljohnston112 avatar Jun 03 '22 22:06 aljohnston112

Hmmm, a rebuild fixed that

aljohnston112 avatar Jun 03 '22 22:06 aljohnston112

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.

dmytroKarataiev avatar Jul 29 '22 14:07 dmytroKarataiev

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)

EdinSe avatar Aug 09 '22 17:08 EdinSe