From 9e2b59060d2ec897690b9a4c060d381f153c7aad Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Wed, 25 Dec 2024 04:29:02 -0800 Subject: [PATCH] Drive app migration tests through instrumentation Make tests less flaky --- app/test/src/main/AndroidManifest.xml | 13 ++- .../topjohnwu/magisk/test/AppMigrationTest.kt | 88 +++++++++++++++++++ .../magisk/test/{TestRunner.kt => Runners.kt} | 8 +- scripts/test_common.sh | 33 ++++--- 4 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 app/test/src/main/java/com/topjohnwu/magisk/test/AppMigrationTest.kt rename app/test/src/main/java/com/topjohnwu/magisk/test/{TestRunner.kt => Runners.kt} (84%) diff --git a/app/test/src/main/AndroidManifest.xml b/app/test/src/main/AndroidManifest.xml index fa7d5c44d..f6db92673 100644 --- a/app/test/src/main/AndroidManifest.xml +++ b/app/test/src/main/AndroidManifest.xml @@ -2,13 +2,22 @@ + + + + + + + android:targetPackage="com.topjohnwu.magisk.test" /> diff --git a/app/test/src/main/java/com/topjohnwu/magisk/test/AppMigrationTest.kt b/app/test/src/main/java/com/topjohnwu/magisk/test/AppMigrationTest.kt new file mode 100644 index 000000000..7123d47eb --- /dev/null +++ b/app/test/src/main/java/com/topjohnwu/magisk/test/AppMigrationTest.kt @@ -0,0 +1,88 @@ +package com.topjohnwu.magisk.test + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.ParcelFileDescriptor.AutoCloseInputStream +import androidx.annotation.Keep +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +@Keep +@RunWith(AndroidJUnit4::class) +class AppMigrationTest { + + companion object { + private const val APP_PKG = "com.topjohnwu.magisk" + private const val STUB_PKG = "repackaged.$APP_PKG" + private const val RECEIVER_TIMEOUT = 20L + } + + private val instrumentation get() = InstrumentationRegistry.getInstrumentation() + private val context get() = instrumentation.context + private val uiAutomation get() = instrumentation.uiAutomation + private val registeredReceivers = mutableListOf() + + class PackageRemoveMonitor( + context: Context, + private val packageName: String + ) : BroadcastReceiver() { + + val latch = CountDownLatch(1) + + init { + val filter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED) + filter.addDataScheme("package") + context.registerReceiver(this, filter) + } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_PACKAGE_REMOVED) + return + val data = intent.data ?: return + val pkg = data.schemeSpecificPart + if (pkg == packageName) latch.countDown() + } + } + + @After + fun tearDown() { + registeredReceivers.forEach(context::unregisterReceiver) + } + + private fun testAppMigration(pkg: String, method: String) { + val receiver = PackageRemoveMonitor(context, pkg) + registeredReceivers.add(receiver) + + // Trigger the test to run migration + val pfd = uiAutomation.executeShellCommand( + "am instrument -w --user 0 -e class .Environment#$method " + + "$pkg.test/${AppTestRunner::class.java.name}" + ) + val output = AutoCloseInputStream(pfd).reader().use { it.readText() } + assertTrue("$method failed, inst out: $output", output.contains("OK (")) + + // Wait for migration to complete + assertTrue( + "$pkg uninstallation failed", + receiver.latch.await(RECEIVER_TIMEOUT, TimeUnit.SECONDS) + ) + } + + @Test + fun testAppHide() { + testAppMigration(APP_PKG, "setupAppHide") + } + + @Test + fun testAppRestore() { + testAppMigration(STUB_PKG, "setupAppRestore") + } +} diff --git a/app/test/src/main/java/com/topjohnwu/magisk/test/TestRunner.kt b/app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt similarity index 84% rename from app/test/src/main/java/com/topjohnwu/magisk/test/TestRunner.kt rename to app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt index e8dd83edb..29a0523da 100644 --- a/app/test/src/main/java/com/topjohnwu/magisk/test/TestRunner.kt +++ b/app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnitRunner -class TestRunner : AndroidJUnitRunner() { +open class TestRunner : AndroidJUnitRunner() { override fun onCreate(arguments: Bundle) { // Support short-hand ".ClassName" arguments.getString("class")?.let { @@ -17,6 +17,12 @@ class TestRunner : AndroidJUnitRunner() { } arguments.putString("class", classArg) } + super.onCreate(arguments) + } +} + +class AppTestRunner : TestRunner() { + override fun onCreate(arguments: Bundle) { // Force using the target context's classloader to run tests arguments.putString("classLoader", TestClassLoader::class.java.name) super.onCreate(arguments) diff --git a/scripts/test_common.sh b/scripts/test_common.sh index 310541b07..c7a5216f2 100644 --- a/scripts/test_common.sh +++ b/scripts/test_common.sh @@ -28,16 +28,9 @@ print_error() { } # $1 = TestClass#method -# $2: boolean = isRepackaged +# $2 = component am_instrument() { - local test_pkg - if [ -n "$2" -a "$2" ]; then - test_pkg="repackaged.com.topjohnwu.magisk.test" - else - test_pkg=com.topjohnwu.magisk.test - fi - local out=$(adb shell am instrument -w --user 0 -e class "$1" \ - "$test_pkg/com.topjohnwu.magisk.test.TestRunner") + local out=$(adb shell am instrument -w --user 0 -e class "$1" "$2") grep -q 'OK (' <<< "$out" } @@ -57,27 +50,31 @@ run_setup() { # Install the test app adb install -r -g out/test.apk + local app='com.topjohnwu.magisk.test/com.topjohnwu.magisk.test.AppTestRunner' + # Run setup through the test app - am_instrument '.Environment#setupMagisk' + am_instrument '.Environment#setupMagisk' $app # Install LSPosed - am_instrument '.Environment#setupLsposed' + am_instrument '.Environment#setupLsposed' $app } run_tests() { + local self='com.topjohnwu.magisk.test/com.topjohnwu.magisk.test.TestRunner' + local app='com.topjohnwu.magisk.test/com.topjohnwu.magisk.test.AppTestRunner' + local stub='repackaged.com.topjohnwu.magisk.test/com.topjohnwu.magisk.test.AppTestRunner' + # Run app tests - am_instrument '.MagiskAppTest,.AdditionalTest' + am_instrument '.MagiskAppTest,.AdditionalTest' $app # Test app hiding - am_instrument '.Environment#setupAppHide' - wait_for_pm com.topjohnwu.magisk + am_instrument '.AppMigrationTest#testAppHide' $self # Make sure it still works - am_instrument '.MagiskAppTest' true + am_instrument '.MagiskAppTest' $stub # Test app restore - am_instrument '.Environment#setupAppRestore' true - wait_for_pm repackaged.com.topjohnwu.magisk + am_instrument '.AppMigrationTest#testAppRestore' $self # Make sure it still works - am_instrument '.MagiskAppTest' + am_instrument '.MagiskAppTest' $app }