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
}