Introduce instrumentation tests

This commit is contained in:
topjohnwu
2024-12-13 01:09:52 -08:00
committed by John Wu
parent 24615afda1
commit 9112a3a4f5
15 changed files with 190 additions and 104 deletions

View File

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

View File

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

View File

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

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

View File

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

@@ -0,0 +1 @@
/build

24
app/test/build.gradle.kts Normal file
View 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)
}

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

View File

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

View File

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