mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-18 22:17:40 +00:00
Introduce instrumentation tests
This commit is contained in:
parent
24615afda1
commit
9112a3a4f5
@ -60,4 +60,9 @@ dependencies {
|
||||
implementation(libs.activity)
|
||||
implementation(libs.collection.ktx)
|
||||
implementation(libs.profileinstaller)
|
||||
|
||||
// We also implement all our tests in this module.
|
||||
// However, we don't want to bundle test dependencies.
|
||||
// That's why we make it compileOnly.
|
||||
compileOnly(libs.test.junit)
|
||||
}
|
||||
|
1
app/core/proguard-rules.pro
vendored
1
app/core/proguard-rules.pro
vendored
@ -37,6 +37,7 @@
|
||||
-flattenpackagehierarchy
|
||||
-allowaccessmodification
|
||||
|
||||
-dontwarn org.junit.Assert
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
|
@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.base.BaseProvider
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import com.topjohnwu.magisk.core.su.TestHandler
|
||||
|
||||
class Provider : BaseProvider() {
|
||||
|
||||
@ -13,7 +12,7 @@ class Provider : BaseProvider() {
|
||||
SuCallbackHandler.run(context!!, method, extras)
|
||||
Bundle.EMPTY
|
||||
}
|
||||
else -> TestHandler.run(method)
|
||||
else -> Bundle.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
app/core/src/main/java/com/topjohnwu/magisk/core/TestImpl.kt
Normal file
50
app/core/src/main/java/com/topjohnwu/magisk/core/TestImpl.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertTrue
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* We implement all test logic here and mark it with @Keep so that our instrumentation package
|
||||
* can properly run tests on fully obfuscated release APKs.
|
||||
*/
|
||||
@Keep
|
||||
object TestImpl {
|
||||
|
||||
fun before() {
|
||||
assertTrue("Should have root access", Shell.getShell().isRoot)
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
}
|
||||
|
||||
object LogList : CallbackList<String>(Runnable::run) {
|
||||
override fun onAddElement(e: String) {
|
||||
Timber.i(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun setupMagisk() {
|
||||
runBlocking {
|
||||
MagiskInstaller.Emulator(LogList, LogList).exec()
|
||||
}
|
||||
}
|
||||
|
||||
fun setupShellGrantTest() {
|
||||
// Clear existing grant for ADB shell
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.delete(2000)
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
}
|
||||
}
|
||||
|
||||
fun testZygisk() {
|
||||
assertTrue("Zygisk should be enabled", Info.isZygiskEnabled)
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.su
|
||||
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
|
||||
object TestHandler {
|
||||
|
||||
object LogList : CallbackList<String>(Runnable::run) {
|
||||
override fun onAddElement(e: String) {
|
||||
Timber.i(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun run(method: String): Bundle {
|
||||
var reason: String? = null
|
||||
|
||||
fun prerequisite(): Boolean {
|
||||
// Make sure the Magisk app can get root
|
||||
val shell = Shell.getShell()
|
||||
if (!shell.isRoot) {
|
||||
reason = "shell not root"
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
return true
|
||||
}
|
||||
|
||||
fun setup(): Boolean {
|
||||
return runBlocking {
|
||||
MagiskInstaller.Emulator(LogList, LogList).exec()
|
||||
}
|
||||
}
|
||||
|
||||
fun test(): Boolean {
|
||||
// Make sure Zygisk works correctly
|
||||
if (!Info.isZygiskEnabled) {
|
||||
reason = "zygisk not enabled"
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear existing grant for ADB shell
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.delete(2000)
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val result = prerequisite() && runCatching {
|
||||
when (method) {
|
||||
"setup" -> setup()
|
||||
"test" -> test()
|
||||
else -> {
|
||||
reason = "unknown method"
|
||||
false
|
||||
}
|
||||
}
|
||||
}.getOrElse {
|
||||
reason = it.stackTraceToString()
|
||||
false
|
||||
}
|
||||
|
||||
return Bundle().apply {
|
||||
putBoolean("result", result)
|
||||
if (reason != null) putString("reason", reason)
|
||||
}
|
||||
}
|
||||
}
|
1
app/test/.gitignore
vendored
Normal file
1
app/test/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
24
app/test/build.gradle.kts
Normal file
24
app/test/build.gradle.kts
Normal file
@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk.test"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk.test"
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
setupAppCommon()
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":app:core"))
|
||||
|
||||
implementation(libs.test.runner)
|
||||
implementation(libs.test.rules)
|
||||
implementation(libs.test.junit)
|
||||
}
|
16
app/test/src/main/AndroidManifest.xml
Normal file
16
app/test/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission tools:node="removeAll" />
|
||||
|
||||
<application tools:node="replace">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.topjohnwu.magisk"
|
||||
android:label="Tests for Magisk" />
|
||||
|
||||
</manifest>
|
@ -0,0 +1,29 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.topjohnwu.magisk.core.TestImpl
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Environment {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
TestImpl.before()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupMagisk() {
|
||||
TestImpl.setupMagisk()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupShellGrantTest() {
|
||||
TestImpl.setupShellGrantTest()
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.topjohnwu.magisk.core.TestImpl
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MagiskAppTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
TestImpl.before()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZygisk() {
|
||||
TestImpl.testZygisk()
|
||||
}
|
||||
}
|
22
build.py
22
build.py
@ -426,19 +426,20 @@ def build_apk(module: str):
|
||||
source = Path(*paths, "build", "outputs", "apk", build_type, apk)
|
||||
target = config["outdir"] / apk
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
return target
|
||||
|
||||
|
||||
def build_app():
|
||||
header("* Building the Magisk app")
|
||||
build_apk(":app:apk")
|
||||
apk = build_apk(":app:apk")
|
||||
|
||||
build_type = "release" if args.release else "debug"
|
||||
|
||||
# Rename apk-variant.apk to app-variant.apk
|
||||
source = config["outdir"] / f"apk-{build_type}.apk"
|
||||
target = config["outdir"] / f"app-{build_type}.apk"
|
||||
source = apk
|
||||
target = apk.parent / apk.name.replace("apk-", "app-")
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
|
||||
# Stub building is directly integrated into the main app
|
||||
# build process. Copy the stub APK into output directory.
|
||||
@ -449,7 +450,14 @@ def build_app():
|
||||
|
||||
def build_stub():
|
||||
header("* Building the stub app")
|
||||
build_apk(":app:stub")
|
||||
apk = build_apk(":app:stub")
|
||||
header(f"Output: {apk}")
|
||||
|
||||
|
||||
def build_test():
|
||||
header("* Building the test app")
|
||||
apk = build_apk(":app:test")
|
||||
header(f"Output: {apk}")
|
||||
|
||||
|
||||
################
|
||||
@ -491,6 +499,7 @@ def cleanup():
|
||||
def build_all():
|
||||
build_native()
|
||||
build_app()
|
||||
build_test()
|
||||
|
||||
|
||||
############
|
||||
@ -719,6 +728,8 @@ def parse_args():
|
||||
|
||||
stub_parser = subparsers.add_parser("stub", help="build the stub app")
|
||||
|
||||
test_parser = subparsers.add_parser("test", help="build the test app")
|
||||
|
||||
clean_parser = subparsers.add_parser("clean", help="cleanup")
|
||||
clean_parser.add_argument(
|
||||
"targets", nargs="*", help="native, cpp, rust, java, or empty to clean all"
|
||||
@ -757,6 +768,7 @@ def parse_args():
|
||||
rustup_parser.set_defaults(func=setup_rustup)
|
||||
app_parser.set_defaults(func=build_app)
|
||||
stub_parser.set_defaults(func=build_stub)
|
||||
test_parser.set_defaults(func=build_test)
|
||||
emu_parser.set_defaults(func=setup_avd)
|
||||
avd_patch_parser.set_defaults(func=patch_avd_file)
|
||||
clean_parser.set_defaults(func=cleanup)
|
||||
|
@ -44,6 +44,9 @@ transition = { module = "androidx.transition:transition", version = "1.5.1" }
|
||||
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.4.5" }
|
||||
material = { module = "com.google.android.material:material", version = "1.12.0" }
|
||||
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" }
|
||||
|
||||
# topjohnwu
|
||||
indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" }
|
||||
|
@ -66,7 +66,7 @@ fi
|
||||
|
||||
# Stop zygote (and previous setup if exists)
|
||||
magisk --stop 2>/dev/null
|
||||
stop zygote
|
||||
stop
|
||||
if [ -d /debug_ramdisk ]; then
|
||||
umount -l /debug_ramdisk 2>/dev/null
|
||||
fi
|
||||
@ -166,7 +166,7 @@ fi
|
||||
|
||||
# Boot up
|
||||
$MAGISKTMP/magisk --post-fs-data
|
||||
start zygote
|
||||
start
|
||||
$MAGISKTMP/magisk --service
|
||||
# Make sure reset nb prop after zygote starts
|
||||
sleep 2
|
||||
|
@ -7,6 +7,7 @@ export PATH="$PATH:$ANDROID_HOME/platform-tools"
|
||||
emu="$ANDROID_HOME/emulator/emulator"
|
||||
sdk="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
|
||||
avd="$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager"
|
||||
test_pkg='com.topjohnwu.magisk.test'
|
||||
|
||||
boot_timeout=600
|
||||
|
||||
@ -23,17 +24,12 @@ print_error() {
|
||||
echo -e "\n\033[41;39m${1}\033[0m\n"
|
||||
}
|
||||
|
||||
run_content_cmd() {
|
||||
while true; do
|
||||
local out=$(adb shell /system/xbin/su 0 content call --uri content://com.topjohnwu.magisk.provider --method $1 | tee /dev/fd/2)
|
||||
if ! grep -q 'Bundle\[' <<< "$out"; then
|
||||
# The call failed, wait a while and retry later
|
||||
sleep 30
|
||||
else
|
||||
grep -q 'result=true' <<< "$out"
|
||||
return $?
|
||||
fi
|
||||
done
|
||||
run_instrument_tests() {
|
||||
local out=$(adb shell am instrument -w \
|
||||
--user 0 \
|
||||
-e class "$1" \
|
||||
com.topjohnwu.magisk.test/androidx.test.runner.AndroidJUnitRunner)
|
||||
grep -q 'OK (' <<< "$out"
|
||||
}
|
||||
|
||||
test_setup() {
|
||||
@ -43,12 +39,18 @@ test_setup() {
|
||||
# Install the Magisk app
|
||||
adb install -r -g out/app-${variant}.apk
|
||||
|
||||
# Use the app to run setup and reboot
|
||||
run_content_cmd setup
|
||||
# Install the test app
|
||||
adb install -r -g out/test-${variant}.apk
|
||||
|
||||
# Run setup through the test app
|
||||
run_instrument_tests "$test_pkg.Environment#setupMagisk"
|
||||
}
|
||||
|
||||
test_app() {
|
||||
# Run app tests
|
||||
run_content_cmd test
|
||||
run_instrument_tests "$test_pkg.MagiskAppTest"
|
||||
|
||||
# Test shell su request
|
||||
run_instrument_tests "$test_pkg.Environment#setupShellGrantTest"
|
||||
adb shell /system/xbin/su 2000 su -c id | tee /dev/fd/2 | grep -q 'uid=0'
|
||||
}
|
||||
|
@ -8,4 +8,4 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
rootProject.name = "Magisk"
|
||||
include(":app:apk", ":app:core", ":app:shared", ":app:stub", ":native")
|
||||
include(":app:apk", ":app:core", ":app:shared", ":app:stub", ":app:test", ":native")
|
||||
|
Loading…
x
Reference in New Issue
Block a user