diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6cfe758ad..9e3684dfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,6 +89,25 @@ jobs: emulator-boot-timeout: 1200 script: bash contrib/instrumentation.sh + - name: create instrumentation coverage for playStore variant + uses: reactivecircus/android-emulator-runner@v2 + env: + GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000" + with: + api-level: ${{ matrix.api-level }} + target: default + arch: x86_64 + profile: pixel_2 + ram-size: 3072M + cores: 4 + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + sdcard-path-or-size: 1024M + disable-animations: true + force-avd-creation: false + heap-size: 512M + emulator-boot-timeout: 1200 + script: bash contrib/instrumentation-release.sh + - name: Test custom app uses: reactivecircus/android-emulator-runner@v2 env: diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aa4308075..3ce7bbb88 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,4 @@ +import com.slack.keeper.optInToKeeper import org.w3c.dom.Element import plugin.KiwixConfigurationPlugin import java.io.StringWriter @@ -11,6 +12,9 @@ plugins { android id("com.github.triplet.play") version Versions.com_github_triplet_play_gradle_plugin } +if (hasProperty("testingMinimizedBuild")) { + apply(plugin = "com.slack.keeper") +} plugins.apply(KiwixConfigurationPlugin::class) apply(from = rootProject.file("jacoco.gradle")) @@ -93,6 +97,14 @@ play { resolutionStrategy.set(com.github.triplet.gradle.androidpublisher.ResolutionStrategy.FAIL) } +androidComponents { + beforeVariants { variantBuilder -> + if (variantBuilder.name == "debug" && hasProperty("testingMinimizedBuild")) { + variantBuilder.optInToKeeper() + } + } +} + dependencies { androidTestImplementation(Libs.leakcanary_android_instrumentation) testImplementation(Libs.kotlinx_coroutines_test) diff --git a/app/test-rules.pro b/app/test-rules.pro new file mode 100644 index 000000000..4a3be90dd --- /dev/null +++ b/app/test-rules.pro @@ -0,0 +1,26 @@ +# TL;DR Proguard/R8 shouldn't mess with androidTest files +-dontskipnonpubliclibraryclassmembers +-dontoptimize +-dontobfuscate +-dontshrink +-ignorewarnings +-dontnote ** +-dontwarn org.easymock.IArgumentMatcher +-dontwarn org.mockito.internal.creation.bytebuddy.MockMethodDispatcher +-dontwarn org.jmock.core.Constraint +-dontwarn org.robolectric.RobolectricTestRunner +-dontwarn org.objectweb.asm.ClassVisitor +-dontwarn javax.servlet.ServletContextListener +-dontwarn java.lang.instrument.ClassFileTransformer +-dontwarn org.objectweb.asm.MethodVisitor +-dontwarn android.net.http.** +-dontwarn com.android.internal.http.multipart.MultipartEntity +-dontwarn java.lang.ClassValue + +-keep class org.yaml.** { *; } +-keep class okreplay.** { *; } +-keepattributes InnerClasses +-keep class **.R +-keep class **.R$* { + ; +} diff --git a/build.gradle.kts b/build.gradle.kts index 22a4f030f..792b02bbe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,7 @@ buildscript { classpath(Libs.com_android_tools_build_gradle) classpath(Libs.kotlin_gradle_plugin) classpath(Libs.navigation_safe_args_gradle_plugin) + classpath(Libs.keeper) // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index e3e3962b4..b3dcac863 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -345,4 +345,9 @@ object Libs { * https://github.com/zxing/zxing */ const val zxing = "com.google.zxing:core:" + Versions.zxing + + /** + * https://github.com/slackhq/keeper + */ + const val keeper = "com.slack.keeper:keeper:" + Versions.keeper } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index af6d7b031..3a2ac01fd 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -103,6 +103,8 @@ object Versions { const val roomVersion = "2.5.0" const val zxing = "3.5.3" + + const val keeper = "0.16.1" } /** diff --git a/buildSrc/src/main/kotlin/plugin/AppConfigurer.kt b/buildSrc/src/main/kotlin/plugin/AppConfigurer.kt index 0fabc0d06..ddd8ecef9 100644 --- a/buildSrc/src/main/kotlin/plugin/AppConfigurer.kt +++ b/buildSrc/src/main/kotlin/plugin/AppConfigurer.kt @@ -52,6 +52,17 @@ class AppConfigurer { File("${target.rootDir}/app", "proguard-rules.pro") ) } + getByName("debug") { + if (target.hasProperty("testingMinimizedBuild")) { + isMinifyEnabled = target.hasProperty("testingMinimizedBuild") + isShrinkResources = target.hasProperty("testingMinimizedBuild") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + File("${target.rootDir}/app", "proguard-rules.pro") + ) + testProguardFile(File("${target.rootDir}/app", "test-rules.pro")) + } + } } val abiCodes = mapOf("arm64-v8a" to 6, "x86" to 3, "x86_64" to 4, "armeabi-v7a" to 5) diff --git a/contrib/instrumentation-customapps.sh b/contrib/instrumentation-customapps.sh index 665bc125c..51e82804b 100644 --- a/contrib/instrumentation-customapps.sh +++ b/contrib/instrumentation-customapps.sh @@ -26,6 +26,8 @@ adb logcat *:E -v color & PACKAGE_NAME="org.kiwix.kiwixmobile.custom" TEST_PACKAGE_NAME="${PACKAGE_NAME}.test" +TEST_SERVICES_PACKAGE="androidx.test.services" +TEST_ORCHESTRATOR_PACKAGE="androidx.test.orchestrator" # Function to check if the application is installed is_app_installed() { adb shell pm list packages | grep -q "$1" @@ -40,6 +42,14 @@ if is_app_installed "$TEST_PACKAGE_NAME"; then # Delete the test application to properly run the test cases. adb uninstall "${TEST_PACKAGE_NAME}" fi + +if is_app_installed "$TEST_SERVICES_PACKAGE"; then + adb uninstall "${TEST_SERVICES_PACKAGE}" +fi + +if is_app_installed "$TEST_ORCHESTRATOR_PACKAGE"; then + adb uninstall "${TEST_ORCHESTRATOR_PACKAGE}" +fi retry=0 while [ $retry -le 3 ]; do if ./gradlew connectedCustomexampleDebugAndroidTest; then @@ -62,6 +72,12 @@ while [ $retry -le 3 ]; do # Delete the test application to properly run the test cases. adb uninstall "${TEST_PACKAGE_NAME}" fi + if is_app_installed "$TEST_SERVICES_PACKAGE"; then + adb uninstall "${TEST_SERVICES_PACKAGE}" + fi + if is_app_installed "$TEST_ORCHESTRATOR_PACKAGE"; then + adb uninstall "${TEST_ORCHESTRATOR_PACKAGE}" + fi ./gradlew clean retry=$(( retry + 1 )) if [ $retry -eq 3 ]; then diff --git a/contrib/instrumentation-release.sh b/contrib/instrumentation-release.sh new file mode 100644 index 000000000..ec309b7ff --- /dev/null +++ b/contrib/instrumentation-release.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +# Enable Wi-Fi on the emulator +adb shell svc wifi enable +adb logcat -c +# shellcheck disable=SC2035 +adb logcat *:E -v color & + +PACKAGE_NAME="org.kiwix.kiwixmobile" +TEST_PACKAGE_NAME="${PACKAGE_NAME}.test" +TEST_SERVICES_PACKAGE="androidx.test.services" +TEST_ORCHESTRATOR_PACKAGE="androidx.test.orchestrator" +# Function to check if the application is installed +is_app_installed() { + adb shell pm list packages | grep -q "$1" +} + +if is_app_installed "$PACKAGE_NAME"; then + adb uninstall "${PACKAGE_NAME}" +fi + +if is_app_installed "$TEST_PACKAGE_NAME"; then + adb uninstall "${TEST_PACKAGE_NAME}" +fi + +if is_app_installed "$TEST_SERVICES_PACKAGE"; then + adb uninstall "${TEST_SERVICES_PACKAGE}" +fi + +if is_app_installed "$TEST_ORCHESTRATOR_PACKAGE"; then + adb uninstall "${TEST_ORCHESTRATOR_PACKAGE}" +fi + +retry=0 +while [ $retry -le 3 ]; do + if ./gradlew connectedDebugAndroidTest -PtestingMinimizedBuild -Pandroid.testInstrumentationRunnerArguments.class=org.kiwix.kiwixmobile.download.DownloadTest "-Dorg.gradle.jvmargs=-Xmx16G -XX:+UseParallelGC" -Dfile.encoding=UTF-8; then + echo "connectedDebugAndroidTest for release variant succeeded" >&2 + break + else + adb kill-server + adb start-server + # Enable Wi-Fi on the emulator + adb shell svc wifi enable + adb logcat -c + # shellcheck disable=SC2035 + adb logcat *:E -v color & + + if is_app_installed "$PACKAGE_NAME"; then + adb uninstall "${PACKAGE_NAME}" + fi + if is_app_installed "$TEST_PACKAGE_NAME"; then + adb uninstall "${TEST_PACKAGE_NAME}" + fi + if is_app_installed "$TEST_SERVICES_PACKAGE"; then + adb uninstall "${TEST_SERVICES_PACKAGE}" + fi + + if is_app_installed "$TEST_ORCHESTRATOR_PACKAGE"; then + adb uninstall "${TEST_ORCHESTRATOR_PACKAGE}" + fi + ./gradlew clean + retry=$(( retry + 1 )) + if [ $retry -eq 3 ]; then + adb exec-out screencap -p >screencap.png + exit 1 + fi + fi +done + diff --git a/contrib/instrumentation.sh b/contrib/instrumentation.sh index f63750893..55628ce13 100644 --- a/contrib/instrumentation.sh +++ b/contrib/instrumentation.sh @@ -8,6 +8,8 @@ adb logcat *:E -v color & PACKAGE_NAME="org.kiwix.kiwixmobile" TEST_PACKAGE_NAME="${PACKAGE_NAME}.test" +TEST_SERVICES_PACKAGE="androidx.test.services" +TEST_ORCHESTRATOR_PACKAGE="androidx.test.orchestrator" # Function to check if the application is installed is_app_installed() { adb shell pm list packages | grep -q "$1" @@ -22,6 +24,14 @@ if is_app_installed "$TEST_PACKAGE_NAME"; then # Delete the test application to properly run the test cases. adb uninstall "${TEST_PACKAGE_NAME}" fi + +if is_app_installed "$TEST_SERVICES_PACKAGE"; then + adb uninstall "${TEST_SERVICES_PACKAGE}" +fi + +if is_app_installed "$TEST_ORCHESTRATOR_PACKAGE"; then + adb uninstall "${TEST_ORCHESTRATOR_PACKAGE}" +fi retry=0 while [ $retry -le 3 ]; do if ./gradlew jacocoInstrumentationTestReport; then @@ -44,6 +54,12 @@ while [ $retry -le 3 ]; do # Delete the test application to properly run the test cases. adb uninstall "${TEST_PACKAGE_NAME}" fi + if is_app_installed "$TEST_SERVICES_PACKAGE"; then + adb uninstall "${TEST_SERVICES_PACKAGE}" + fi + if is_app_installed "$TEST_ORCHESTRATOR_PACKAGE"; then + adb uninstall "${TEST_ORCHESTRATOR_PACKAGE}" + fi ./gradlew clean retry=$(( retry + 1 )) if [ $retry -eq 3 ]; then