Install and test LSPosed through test app

This commit is contained in:
topjohnwu 2024-12-24 17:11:08 -08:00 committed by John Wu
parent 32faa4ced6
commit 2baedf74d1
8 changed files with 136 additions and 49 deletions

View File

@ -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)
}

View File

@ -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))
)
}
}

View File

@ -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<String>(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<String>(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)
)
}
}

View File

@ -26,4 +26,5 @@ dependencies {
implementation(libs.test.runner)
implementation(libs.test.rules)
implementation(libs.test.junit)
implementation(libs.test.uiautomator)
}

View File

@ -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)

View File

@ -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" }

View File

@ -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

View File

@ -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'
}