From 2baedf74d1d738f9d39c3c7ebcae67b850a81e1f Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Tue, 24 Dec 2024 17:11:08 -0800 Subject: [PATCH] Install and test LSPosed through test app --- app/core/build.gradle.kts | 1 + .../topjohnwu/magisk/test/AdditionalTest.kt | 57 +++++++++++++++++ .../com/topjohnwu/magisk/test/Environment.kt | 64 ++++++++++++++++--- app/test/build.gradle.kts | 1 + .../com/topjohnwu/magisk/test/TestRunner.kt | 11 ++++ gradle/libs.versions.toml | 1 + scripts/avd_test.sh | 29 --------- scripts/test_common.sh | 21 +++--- 8 files changed, 136 insertions(+), 49 deletions(-) create mode 100644 app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt diff --git a/app/core/build.gradle.kts b/app/core/build.gradle.kts index 0f8f54cb8..d8569a6ed 100644 --- a/app/core/build.gradle.kts +++ b/app/core/build.gradle.kts @@ -65,4 +65,5 @@ dependencies { // However, we don't want to bundle test dependencies. // That's why we make it compileOnly. compileOnly(libs.test.junit) + compileOnly(libs.test.uiautomator) } diff --git a/app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt b/app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt new file mode 100644 index 000000000..f157ebe8c --- /dev/null +++ b/app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt @@ -0,0 +1,57 @@ +package com.topjohnwu.magisk.test + +import android.app.UiAutomation +import androidx.annotation.Keep +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import org.junit.After +import org.junit.Assert.assertNotNull +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern + +@Keep +@RunWith(AndroidJUnit4::class) +class AdditionalTest { + + companion object { + private const val SHELL_PKG = "com.android.shell" + private const val LSPOSED_CATEGORY = "org.lsposed.manager.LAUNCH_MANAGER" + private const val LSPOSED_PKG = "org.lsposed.manager" + } + + private lateinit var uiAutomation: UiAutomation + private lateinit var device: UiDevice + + @Before + fun setup() { + val inst = InstrumentationRegistry.getInstrumentation() + uiAutomation = inst.uiAutomation + device = UiDevice.getInstance(inst) + } + + @After + fun teardown() { + device.pressHome() + } + + @Test + fun testLaunchLsposedManager() { + assumeTrue(Environment.lsposed()) + + uiAutomation.executeShellCommand( + "am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity" + ) + val pattern = Pattern.compile("$LSPOSED_PKG:id/.*") + assertNotNull( + "LSPosed manager launch failed", + device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10)) + ) + } +} diff --git a/app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt b/app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt index 6f304c564..dccbea056 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt @@ -1,17 +1,27 @@ package com.topjohnwu.magisk.test +import android.app.Notification +import android.content.Context +import android.os.Build import androidx.annotation.Keep +import androidx.core.net.toUri import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.di.ServiceLocator +import com.topjohnwu.magisk.core.download.DownloadNotifier +import com.topjohnwu.magisk.core.download.DownloadProcessor +import com.topjohnwu.magisk.core.ktx.cachedFile import com.topjohnwu.magisk.core.model.su.SuPolicy import com.topjohnwu.magisk.core.tasks.AppMigration +import com.topjohnwu.magisk.core.tasks.FlashZip import com.topjohnwu.magisk.core.tasks.MagiskInstaller import com.topjohnwu.superuser.CallbackList import kotlinx.coroutines.runBlocking import org.junit.Assert.assertTrue +import org.junit.Assume.assumeTrue +import org.junit.Before import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith @@ -25,19 +35,55 @@ class Environment { @BeforeClass @JvmStatic fun before() = MagiskAppTest.before() + + fun lsposed(): Boolean { + return Build.VERSION.SDK_INT >= 27 && Build.VERSION.SDK_INT <= 34 + } + + private const val LSPOSED_URL = + "https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip" + } + + object TimberLog : CallbackList(Runnable::run) { + override fun onAddElement(e: String) { + Timber.i(e) + } + } + + private lateinit var mContext: Context + + @Before + fun setup() { + mContext = InstrumentationRegistry.getInstrumentation().targetContext } @Test fun setupMagisk() { - val log = object : CallbackList(Runnable::run) { - override fun onAddElement(e: String) { - Timber.i(e) - } - } runBlocking { assertTrue( "Magisk setup failed", - MagiskInstaller.Emulator(log, log).exec() + MagiskInstaller.Emulator(TimberLog, TimberLog).exec() + ) + } + } + + @Test + fun setupLsposed() { + assumeTrue(lsposed()) + + val notify = object : DownloadNotifier { + override val context = mContext + override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {} + } + val processor = DownloadProcessor(notify) + val zip = mContext.cachedFile("lsposed.zip") + runBlocking { + ServiceLocator.networkService.fetchFile(LSPOSED_URL).byteStream().use { + processor.handleModule(it, zip.toUri()) + } + assertTrue( + "LSPosed installation failed", + FlashZip(zip.toUri(), TimberLog, TimberLog).exec() ) } } @@ -65,7 +111,7 @@ class Environment { assertTrue( "App hiding failed", AppMigration.patchAndHide( - context = InstrumentationRegistry.getInstrumentation().targetContext, + context = mContext, label = "Settings", pkg = "repackaged.$APP_PACKAGE_NAME" ) @@ -78,9 +124,7 @@ class Environment { runBlocking { assertTrue( "App restoration failed", - AppMigration.restoreApp( - context = InstrumentationRegistry.getInstrumentation().targetContext - ) + AppMigration.restoreApp(mContext) ) } } diff --git a/app/test/build.gradle.kts b/app/test/build.gradle.kts index 92ae38c9f..c3c8ff705 100644 --- a/app/test/build.gradle.kts +++ b/app/test/build.gradle.kts @@ -26,4 +26,5 @@ dependencies { implementation(libs.test.runner) implementation(libs.test.rules) implementation(libs.test.junit) + implementation(libs.test.uiautomator) } diff --git a/app/test/src/main/java/com/topjohnwu/magisk/test/TestRunner.kt b/app/test/src/main/java/com/topjohnwu/magisk/test/TestRunner.kt index c92080a96..e8dd83edb 100644 --- a/app/test/src/main/java/com/topjohnwu/magisk/test/TestRunner.kt +++ b/app/test/src/main/java/com/topjohnwu/magisk/test/TestRunner.kt @@ -6,6 +6,17 @@ import androidx.test.runner.AndroidJUnitRunner class TestRunner : AndroidJUnitRunner() { override fun onCreate(arguments: Bundle) { + // Support short-hand ".ClassName" + arguments.getString("class")?.let { + val classArg = it.split(",").joinToString(separator = ",") { clz -> + if (clz.startsWith(".")) { + "com.topjohnwu.magisk.test$clz" + } else { + clz + } + } + arguments.putString("class", classArg) + } // Force using the target context's classloader to run tests arguments.putString("classLoader", TestClassLoader::class.java.name) super.onCreate(arguments) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0e259f0a7..d14c6a117 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,6 +47,7 @@ jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.3 test-runner = { module = "androidx.test:runner", version = "1.6.2" } test-rules = { module = "androidx.test:rules", version = "1.6.1" } test-junit = { module = "androidx.test.ext:junit", version = "1.2.1" } +test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" } # topjohnwu indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" } diff --git a/scripts/avd_test.sh b/scripts/avd_test.sh index fb6e9f7e9..2a9eb6264 100755 --- a/scripts/avd_test.sh +++ b/scripts/avd_test.sh @@ -4,13 +4,10 @@ set -xe . scripts/test_common.sh emu_args_base="-no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -read-only -no-snapshot -cores $core_count" -lsposed_url='https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip' emu_pid= atd_min_api=30 atd_max_api=35 -lsposed_min_api=27 -lsposed_max_api=34 huge_ram_min_api=26 cleanup() { @@ -81,35 +78,10 @@ test_emu() { run_setup $variant - local lsposed - if [ $api -ge $lsposed_min_api -a $api -le $lsposed_max_api ]; then - lsposed=true - else - lsposed=false - fi - - # Install LSPosed - if $lsposed; then - adb push out/lsposed.zip /data/local/tmp/lsposed.zip - echo 'PATH=$PATH:/debug_ramdisk magisk --install-module /data/local/tmp/lsposed.zip' | adb shell /system/xbin/su - fi - adb reboot wait_emu wait_for_boot run_tests - - # Try to launch LSPosed - if $lsposed; then - adb shell rm -f /data/local/tmp/window_dump.xml - adb shell am start -c org.lsposed.manager.LAUNCH_MANAGER com.android.shell/.BugreportWarningActivity - while adb shell '[ ! -f /data/local/tmp/window_dump.xml ]'; do - sleep 10 - adb shell uiautomator dump /data/local/tmp/window_dump.xml - done - adb shell grep -q org.lsposed.manager /data/local/tmp/window_dump.xml - adb pull /data/local/tmp/window_dump.xml - fi } test_main() { @@ -220,7 +192,6 @@ if [ -n "$FORCE_32_BIT" ]; then fi yes | "$sdk" --licenses > /dev/null -curl -L $lsposed_url -o out/lsposed.zip "$sdk" --channel=3 platform-tools emulator adb kill-server diff --git a/scripts/test_common.sh b/scripts/test_common.sh index a9eb4e8e0..341de8e4a 100644 --- a/scripts/test_common.sh +++ b/scripts/test_common.sh @@ -36,8 +36,7 @@ am_instrument() { else test_pkg=com.topjohnwu.magisk.test fi - local out=$(adb shell am instrument -w --user 0 \ - -e class "com.topjohnwu.magisk.test.$1" \ + local out=$(adb shell am instrument -w --user 0 -e class "$1" \ "$test_pkg/com.topjohnwu.magisk.test.TestRunner") grep -q 'OK (' <<< "$out" } @@ -59,34 +58,36 @@ run_setup() { adb install -r -g out/test.apk # Run setup through the test app - am_instrument 'Environment#setupMagisk' + am_instrument '.Environment#setupMagisk' + # Install LSPosed + am_instrument '.Environment#setupLsposed' } run_tests() { # Run app tests - am_instrument 'MagiskAppTest' + am_instrument '.MagiskAppTest,.AdditionalTest' # Test shell su request - am_instrument 'Environment#setupShellGrantTest' + am_instrument '.Environment#setupShellGrantTest' adb shell /system/xbin/su 2000 su -c id | tee /dev/fd/2 | grep -q 'uid=0' adb shell am force-stop com.topjohnwu.magisk # Test app hiding - am_instrument 'Environment#setupAppHide' + am_instrument '.Environment#setupAppHide' wait_for_pm com.topjohnwu.magisk # Make sure it still works - am_instrument 'MagiskAppTest' true + am_instrument '.MagiskAppTest' true # Test shell su request - am_instrument 'Environment#setupShellGrantTest' true + am_instrument '.Environment#setupShellGrantTest' true adb shell /system/xbin/su 2000 su -c id | tee /dev/fd/2 | grep -q 'uid=0' adb shell am force-stop repackaged.com.topjohnwu.magisk # Test app restore - am_instrument 'Environment#setupAppRestore' true + am_instrument '.Environment#setupAppRestore' true wait_for_pm repackaged.com.topjohnwu.magisk # Make sure it still works - am_instrument 'MagiskAppTest' + am_instrument '.MagiskAppTest' }