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.activity)
|
||||||
implementation(libs.collection.ktx)
|
implementation(libs.collection.ktx)
|
||||||
implementation(libs.profileinstaller)
|
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
|
-flattenpackagehierarchy
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
|
|
||||||
|
-dontwarn org.junit.Assert
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||||
|
@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.topjohnwu.magisk.core.base.BaseProvider
|
import com.topjohnwu.magisk.core.base.BaseProvider
|
||||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
import com.topjohnwu.magisk.core.su.TestHandler
|
|
||||||
|
|
||||||
class Provider : BaseProvider() {
|
class Provider : BaseProvider() {
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ class Provider : BaseProvider() {
|
|||||||
SuCallbackHandler.run(context!!, method, extras)
|
SuCallbackHandler.run(context!!, method, extras)
|
||||||
Bundle.EMPTY
|
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)
|
source = Path(*paths, "build", "outputs", "apk", build_type, apk)
|
||||||
target = config["outdir"] / apk
|
target = config["outdir"] / apk
|
||||||
mv(source, target)
|
mv(source, target)
|
||||||
header(f"Output: {target}")
|
return target
|
||||||
|
|
||||||
|
|
||||||
def build_app():
|
def build_app():
|
||||||
header("* Building the Magisk app")
|
header("* Building the Magisk app")
|
||||||
build_apk(":app:apk")
|
apk = build_apk(":app:apk")
|
||||||
|
|
||||||
build_type = "release" if args.release else "debug"
|
build_type = "release" if args.release else "debug"
|
||||||
|
|
||||||
# Rename apk-variant.apk to app-variant.apk
|
# Rename apk-variant.apk to app-variant.apk
|
||||||
source = config["outdir"] / f"apk-{build_type}.apk"
|
source = apk
|
||||||
target = config["outdir"] / f"app-{build_type}.apk"
|
target = apk.parent / apk.name.replace("apk-", "app-")
|
||||||
mv(source, target)
|
mv(source, target)
|
||||||
|
header(f"Output: {target}")
|
||||||
|
|
||||||
# Stub building is directly integrated into the main app
|
# Stub building is directly integrated into the main app
|
||||||
# build process. Copy the stub APK into output directory.
|
# build process. Copy the stub APK into output directory.
|
||||||
@ -449,7 +450,14 @@ def build_app():
|
|||||||
|
|
||||||
def build_stub():
|
def build_stub():
|
||||||
header("* Building the stub app")
|
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():
|
def build_all():
|
||||||
build_native()
|
build_native()
|
||||||
build_app()
|
build_app()
|
||||||
|
build_test()
|
||||||
|
|
||||||
|
|
||||||
############
|
############
|
||||||
@ -719,6 +728,8 @@ def parse_args():
|
|||||||
|
|
||||||
stub_parser = subparsers.add_parser("stub", help="build the stub app")
|
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 = subparsers.add_parser("clean", help="cleanup")
|
||||||
clean_parser.add_argument(
|
clean_parser.add_argument(
|
||||||
"targets", nargs="*", help="native, cpp, rust, java, or empty to clean all"
|
"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)
|
rustup_parser.set_defaults(func=setup_rustup)
|
||||||
app_parser.set_defaults(func=build_app)
|
app_parser.set_defaults(func=build_app)
|
||||||
stub_parser.set_defaults(func=build_stub)
|
stub_parser.set_defaults(func=build_stub)
|
||||||
|
test_parser.set_defaults(func=build_test)
|
||||||
emu_parser.set_defaults(func=setup_avd)
|
emu_parser.set_defaults(func=setup_avd)
|
||||||
avd_patch_parser.set_defaults(func=patch_avd_file)
|
avd_patch_parser.set_defaults(func=patch_avd_file)
|
||||||
clean_parser.set_defaults(func=cleanup)
|
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" }
|
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.4.5" }
|
||||||
material = { module = "com.google.android.material:material", version = "1.12.0" }
|
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" }
|
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
|
# topjohnwu
|
||||||
indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" }
|
indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" }
|
||||||
|
@ -66,7 +66,7 @@ fi
|
|||||||
|
|
||||||
# Stop zygote (and previous setup if exists)
|
# Stop zygote (and previous setup if exists)
|
||||||
magisk --stop 2>/dev/null
|
magisk --stop 2>/dev/null
|
||||||
stop zygote
|
stop
|
||||||
if [ -d /debug_ramdisk ]; then
|
if [ -d /debug_ramdisk ]; then
|
||||||
umount -l /debug_ramdisk 2>/dev/null
|
umount -l /debug_ramdisk 2>/dev/null
|
||||||
fi
|
fi
|
||||||
@ -166,7 +166,7 @@ fi
|
|||||||
|
|
||||||
# Boot up
|
# Boot up
|
||||||
$MAGISKTMP/magisk --post-fs-data
|
$MAGISKTMP/magisk --post-fs-data
|
||||||
start zygote
|
start
|
||||||
$MAGISKTMP/magisk --service
|
$MAGISKTMP/magisk --service
|
||||||
# Make sure reset nb prop after zygote starts
|
# Make sure reset nb prop after zygote starts
|
||||||
sleep 2
|
sleep 2
|
||||||
|
@ -7,6 +7,7 @@ export PATH="$PATH:$ANDROID_HOME/platform-tools"
|
|||||||
emu="$ANDROID_HOME/emulator/emulator"
|
emu="$ANDROID_HOME/emulator/emulator"
|
||||||
sdk="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
|
sdk="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
|
||||||
avd="$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager"
|
avd="$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager"
|
||||||
|
test_pkg='com.topjohnwu.magisk.test'
|
||||||
|
|
||||||
boot_timeout=600
|
boot_timeout=600
|
||||||
|
|
||||||
@ -23,17 +24,12 @@ print_error() {
|
|||||||
echo -e "\n\033[41;39m${1}\033[0m\n"
|
echo -e "\n\033[41;39m${1}\033[0m\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_content_cmd() {
|
run_instrument_tests() {
|
||||||
while true; do
|
local out=$(adb shell am instrument -w \
|
||||||
local out=$(adb shell /system/xbin/su 0 content call --uri content://com.topjohnwu.magisk.provider --method $1 | tee /dev/fd/2)
|
--user 0 \
|
||||||
if ! grep -q 'Bundle\[' <<< "$out"; then
|
-e class "$1" \
|
||||||
# The call failed, wait a while and retry later
|
com.topjohnwu.magisk.test/androidx.test.runner.AndroidJUnitRunner)
|
||||||
sleep 30
|
grep -q 'OK (' <<< "$out"
|
||||||
else
|
|
||||||
grep -q 'result=true' <<< "$out"
|
|
||||||
return $?
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test_setup() {
|
test_setup() {
|
||||||
@ -43,12 +39,18 @@ test_setup() {
|
|||||||
# Install the Magisk app
|
# Install the Magisk app
|
||||||
adb install -r -g out/app-${variant}.apk
|
adb install -r -g out/app-${variant}.apk
|
||||||
|
|
||||||
# Use the app to run setup and reboot
|
# Install the test app
|
||||||
run_content_cmd setup
|
adb install -r -g out/test-${variant}.apk
|
||||||
|
|
||||||
|
# Run setup through the test app
|
||||||
|
run_instrument_tests "$test_pkg.Environment#setupMagisk"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_app() {
|
test_app() {
|
||||||
# Run app tests
|
# 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'
|
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"
|
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