'Gradle ignores testProguardFile when running Android instrumentation

I'm trying to run instrumentation tests on a release build type. My setup is as follows:

Android Studio - 3.4.1
Android Gradle Plugin - 3.4.1
Gragle - 5.4.1
R8 - Enabled (default)

Relevant build.gradle snippet:

    testBuildType "release"

    buildTypes {
        release {
            proguardFiles fileTree(dir: 'vendor', include: ['*.pro']).asList().toArray()
            debuggable true
            minifyEnabled true
            shrinkResources true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            testProguardFile 'proguard-rules-test.pro'
            testCoverageEnabled false
        }
    }

Content of proguard-rules-test.pro (for test purposes):

-keep public class ** { *; }

Running any instrumentation results in the following runtime exception:

com.MYAPP.debug E/InstrumentationResultPrinter: Failed to mark test No Tests as finished after process crash
com.MYAPP.debug E/MonitoringInstr: Exception encountered by: Thread[main,5,main]. Dumping thread state to outputs and pining for the fjords.
    java.lang.NoSuchMethodError: No virtual method setAppComponent(L/com/MYAPP/injection/AppComponent;)V in class L/com/MYAPP/data/common/MyApplication$Companion; or its super classes (declaration of 'com.MYAPP.data.common.MyApplication$Companion' appears in /data/app/com.MYAPP.debug-o3QrzyIOGC0Ko3XRS2fcxQ==/base.apk)
        at com.MYAPP.base.TestMyApplication.h(TestMyApplication.kt:20)
        at com.MYAPP.data.common.MyApplication.onCreate(MyApplication.kt:126)

(TestMyApplication extends MyApplication and being called by AndroidJUnitRunner)

Moving the -keep line from proguard-rules-test.pro into the main Proguard rule file makes the tests to run and pass without issues.

Any ideas?



Solution 1:[1]

It may not be obvious from sgjesse's answer, but this is happening at two different places. The tasks for running instrumentation tests on minified input is roughly as follows:

...

  1. task compileDebugWithJavaC task

  2. task compileClassesAndResourcesWithR8ForDebug

  3. task compileDebugTestWithJavaC

  4. task compileClassesAndResourcesWithR8ForDebugTest

In the tasks above, 1. and 2. compiles you app and optimizes it with R8, 3. and 4. compiles your tests and compiles the tests with R8, having the original app on library-path

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' are applied to 2. when you compile your app with R8

testProguardFile 'proguard-rules-test.pro' is applied to the tests when you compile the tests with R8. That is the reason why adding the keep rules to the "normal" proguard files works, because you are adding them to late.

If you want to have rules applied to your main app when running debug, just add a debug configuration and add another file in there:

buildTypes{
  debug {
   ..
   proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-rules-test.pro'
  }

In all cases, I do not understand why you are "testing" your minified app if you add -keep public class ** { *; }. This will basically prevent R8 from doing any optimizations so you might as well test without minifyEnabled. Ideally you should find the your tests entry-points in your app and only add those. This is of course not as easy with generated companion classes in kotlin.

Solution 2:[2]

Wrestled with this for a couple of days, and it does seem that for whatever reason recent versions of gradle/proGuard seem to ignore that setting. (testProguardFile("your-file.pro"))

Found a really clean and simple way to do it using some conditional logic inside your release.

Below is the version in kotlin dsl, so build.gradle.kts but no doubt there will be the Groovy equivalent if using regular build.gradle:

     getByName("release") {
            signingConfig = signingConfigs.getByName("release")
            isDebuggable = false
            // any other release config as normal
            isMinifyEnabled = !gradle.startParameter.taskNames.any { it.contains("AndroidTest") }
            proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
    }

Hopefully self-explanatory, but if not, then isMinifyEnabled = !gradle.startParameter.taskNames.any { it.contains("AndroidTest") } basically says DON'T MINIFY if the gradle task that kicked off the build has "AndroidTest" in it somewhere.

Confirmed this still minifies the release apk, and now any minification for the test apk is totally turned off, and all unit tests now run fine on the release apk.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 MortenKJ
Solution 2 Vin Norman