mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-04-21 10:51:28 +00:00
Add new tests for app hiding
This commit is contained in:
parent
820710c086
commit
5885b8c20d
11
app/core/proguard-rules.pro
vendored
11
app/core/proguard-rules.pro
vendored
@ -37,13 +37,4 @@
|
|||||||
-flattenpackagehierarchy
|
-flattenpackagehierarchy
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
|
|
||||||
-dontwarn org.junit.Assert
|
-dontwarn org.junit.**
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
|
||||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
|
||||||
-dontwarn org.commonmark.ext.gfm.strikethrough.Strikethrough
|
|
||||||
-dontwarn org.conscrypt.Conscrypt*
|
|
||||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
|
||||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
|
||||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
|
||||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
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,17 +1,18 @@
|
|||||||
package com.topjohnwu.magisk.core.model.su
|
package com.topjohnwu.magisk.core.model.su
|
||||||
|
|
||||||
class SuPolicy(val uid: Int) {
|
class SuPolicy(
|
||||||
|
val uid: Int,
|
||||||
|
var policy: Int = INTERACTIVE,
|
||||||
|
var until: Long = -1L,
|
||||||
|
var logging: Boolean = true,
|
||||||
|
var notification: Boolean = true,
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val INTERACTIVE = 0
|
const val INTERACTIVE = 0
|
||||||
const val DENY = 1
|
const val DENY = 1
|
||||||
const val ALLOW = 2
|
const val ALLOW = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
var policy: Int = INTERACTIVE
|
|
||||||
var until: Long = -1L
|
|
||||||
var logging: Boolean = true
|
|
||||||
var notification: Boolean = true
|
|
||||||
|
|
||||||
fun toMap(): MutableMap<String, Any> = mutableMapOf(
|
fun toMap(): MutableMap<String, Any> = mutableMapOf(
|
||||||
"uid" to uid,
|
"uid" to uid,
|
||||||
"policy" to policy,
|
"policy" to policy,
|
||||||
|
@ -62,7 +62,7 @@ class SuRequestHandler(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
output = File(fifo)
|
output = File(fifo)
|
||||||
policy = SuPolicy(uid)
|
policy = policyDB.fetch(uid) ?: SuPolicy(uid)
|
||||||
try {
|
try {
|
||||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
||||||
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.app.ActivityOptions
|
import android.app.ActivityOptions
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.StubApk
|
import com.topjohnwu.magisk.StubApk
|
||||||
@ -25,7 +26,6 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
@ -36,6 +36,7 @@ object AppMigration {
|
|||||||
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
|
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
|
||||||
private const val ALPHADOTS = "$ALPHA....."
|
private const val ALPHADOTS = "$ALPHA....."
|
||||||
private const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
private const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||||
|
private const val TEST_PKG_NAME = "$APP_PACKAGE_NAME.test"
|
||||||
|
|
||||||
// Some arbitrary limit
|
// Some arbitrary limit
|
||||||
const val MAX_LABEL_LENGTH = 32
|
const val MAX_LABEL_LENGTH = 32
|
||||||
@ -131,21 +132,15 @@ object AppMigration {
|
|||||||
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||||
val xml = AXML(jar.getRawData(je))
|
val xml = AXML(jar.getRawData(je))
|
||||||
val generator = classNameGenerator()
|
val generator = classNameGenerator()
|
||||||
|
val p = xml.patchStrings {
|
||||||
if (!xml.patchStrings {
|
when {
|
||||||
for (i in it.indices) {
|
it.contains(APP_PACKAGE_NAME) -> it.replace(APP_PACKAGE_NAME, pkg)
|
||||||
val s = it[i]
|
it.contains(PLACEHOLDER) -> generator.next()
|
||||||
if (s.contains(APP_PACKAGE_NAME)) {
|
it == origLabel -> label.toString()
|
||||||
it[i] = s.replace(APP_PACKAGE_NAME, pkg)
|
else -> it
|
||||||
} else if (s.contains(PLACEHOLDER)) {
|
|
||||||
it[i] = generator.next()
|
|
||||||
} else if (s == origLabel) {
|
|
||||||
it[i] = label.toString()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}) {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
if (!p) return false
|
||||||
|
|
||||||
// Write apk changes
|
// Write apk changes
|
||||||
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
||||||
@ -159,40 +154,83 @@ object AppMigration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchApp(activity: Activity, pkg: String) {
|
private fun patchTest(apk: File, out: File, pkg: String): Boolean {
|
||||||
val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
try {
|
||||||
|
JarMap.open(apk, true).use { jar ->
|
||||||
|
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||||
|
val xml = AXML(jar.getRawData(je))
|
||||||
|
val p = xml.patchStrings {
|
||||||
|
when (it) {
|
||||||
|
APP_PACKAGE_NAME -> pkg
|
||||||
|
TEST_PKG_NAME -> "$pkg.test"
|
||||||
|
else -> it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!p) return false
|
||||||
|
|
||||||
|
// Write apk changes
|
||||||
|
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
||||||
|
val keys = Keygen()
|
||||||
|
out.outputStream().use { SignApk.sign(keys.cert, keys.key, jar, it) }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchApp(context: Context, pkg: String) {
|
||||||
|
val intent = context.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||||
intent.putExtra(Const.Key.PREV_CONFIG, Config.toBundle())
|
intent.putExtra(Const.Key.PREV_CONFIG, Config.toBundle())
|
||||||
val options = ActivityOptions.makeBasic()
|
val options = ActivityOptions.makeBasic()
|
||||||
if (Build.VERSION.SDK_INT >= 34) {
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
options.setShareIdentityEnabled(true)
|
options.setShareIdentityEnabled(true)
|
||||||
}
|
}
|
||||||
activity.startActivity(intent, options.toBundle())
|
context.startActivity(intent, options.toBundle())
|
||||||
activity.finish()
|
if (context is Activity) {
|
||||||
|
context.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun patchAndHide(activity: Activity, label: String): Boolean {
|
suspend fun patchAndHide(context: Context, label: String, pkg: String? = null): Boolean {
|
||||||
val stub = File(activity.cacheDir, "stub.apk")
|
val stub = File(context.cacheDir, "stub.apk")
|
||||||
try {
|
try {
|
||||||
activity.assets.open("stub.apk").writeTo(stub)
|
context.assets.open("stub.apk").writeTo(stub)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a new random package name and signature
|
// Generate a new random signature and package name if needed
|
||||||
val repack = File(activity.cacheDir, "patched.apk")
|
val pkg = pkg ?: genPackageName()
|
||||||
val pkg = genPackageName()
|
|
||||||
Config.keyStoreRaw = ""
|
Config.keyStoreRaw = ""
|
||||||
|
|
||||||
if (!patch(activity, stub, FileOutputStream(repack), pkg, label))
|
// Check and patch the test APK
|
||||||
return false
|
try {
|
||||||
|
val info = context.packageManager.getApplicationInfo(TEST_PKG_NAME, 0)
|
||||||
|
val testApk = File(info.sourceDir)
|
||||||
|
val testRepack = File(context.cacheDir, "test.apk")
|
||||||
|
if (!patchTest(testApk, testRepack, pkg))
|
||||||
|
return false
|
||||||
|
val cmd = "adb_pm_install $testRepack $pkg.test"
|
||||||
|
if (!Shell.cmd(cmd).exec().isSuccess)
|
||||||
|
return false
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
val repack = File(context.cacheDir, "patched.apk")
|
||||||
|
repack.outputStream().use {
|
||||||
|
if (!patch(context, stub, it, pkg, label))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Install and auto launch app
|
// Install and auto launch app
|
||||||
val cmd = "adb_pm_install $repack $pkg"
|
val cmd = "adb_pm_install $repack $pkg"
|
||||||
if (Shell.cmd(cmd).exec().isSuccess) {
|
if (Shell.cmd(cmd).exec().isSuccess) {
|
||||||
Config.suManager = pkg
|
Config.suManager = pkg
|
||||||
Shell.cmd("touch $AppApkPath").exec()
|
Shell.cmd("touch $AppApkPath").exec()
|
||||||
launchApp(activity, pkg)
|
launchApp(context, pkg)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -216,6 +254,18 @@ object AppMigration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun restoreApp(context: Context): Boolean {
|
||||||
|
val apk = StubApk.current(context)
|
||||||
|
val cmd = "adb_pm_install $apk $APP_PACKAGE_NAME"
|
||||||
|
if (Shell.cmd(cmd).await().isSuccess) {
|
||||||
|
Config.suManager = ""
|
||||||
|
Shell.cmd("touch $AppApkPath").exec()
|
||||||
|
launchApp(context, APP_PACKAGE_NAME)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
suspend fun restore(activity: Activity) {
|
suspend fun restore(activity: Activity) {
|
||||||
val dialog = android.app.ProgressDialog(activity).apply {
|
val dialog = android.app.ProgressDialog(activity).apply {
|
||||||
@ -224,13 +274,7 @@ object AppMigration {
|
|||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
val apk = StubApk.current(activity)
|
if (!restoreApp(activity)) {
|
||||||
val cmd = "adb_pm_install $apk $APP_PACKAGE_NAME"
|
|
||||||
if (Shell.cmd(cmd).await().isSuccess) {
|
|
||||||
Config.suManager = ""
|
|
||||||
Shell.cmd("touch $AppApkPath").exec()
|
|
||||||
launchApp(activity, APP_PACKAGE_NAME)
|
|
||||||
} else {
|
|
||||||
activity.toast(R.string.failure, Toast.LENGTH_LONG)
|
activity.toast(R.string.failure, Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
@ -29,7 +29,7 @@ class AXML(b: ByteArray) {
|
|||||||
* Followed by an array of uint32_t with size = number of strings
|
* Followed by an array of uint32_t with size = number of strings
|
||||||
* Each entry points to an offset into the string data
|
* Each entry points to an offset into the string data
|
||||||
*/
|
*/
|
||||||
fun patchStrings(patchFn: (Array<String>) -> Unit): Boolean {
|
fun patchStrings(mapFn: (String) -> String): Boolean {
|
||||||
val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)
|
val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)
|
||||||
|
|
||||||
fun findStringPool(): Int {
|
fun findStringPool(): Int {
|
||||||
@ -65,7 +65,9 @@ class AXML(b: ByteArray) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val strArr = strList.toTypedArray()
|
val strArr = strList.toTypedArray()
|
||||||
patchFn(strArr)
|
for (i in strArr.indices) {
|
||||||
|
strArr[i] = mapFn(strArr[i])
|
||||||
|
}
|
||||||
|
|
||||||
// Write everything before string data, will patch values later
|
// Write everything before string data, will patch values later
|
||||||
val baos = RawByteStream()
|
val baos = RawByteStream()
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.topjohnwu.magisk.test
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
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.model.su.SuPolicy
|
||||||
|
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||||
|
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||||
|
import com.topjohnwu.superuser.CallbackList
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class Environment {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@BeforeClass
|
||||||
|
@JvmStatic
|
||||||
|
fun before() = MagiskAppTest.before()
|
||||||
|
}
|
||||||
|
|
||||||
|
@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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setupShellGrantTest() {
|
||||||
|
runBlocking {
|
||||||
|
// Inject an undetermined + mute logging policy for ADB shell
|
||||||
|
val policy = SuPolicy(
|
||||||
|
uid = 2000,
|
||||||
|
logging = false,
|
||||||
|
notification = false,
|
||||||
|
until = 0L
|
||||||
|
)
|
||||||
|
ServiceLocator.policyDB.update(policy)
|
||||||
|
// Bypass the need to actually show a dialog
|
||||||
|
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||||
|
Config.prefs.edit().commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setupAppHide() {
|
||||||
|
runBlocking {
|
||||||
|
assertTrue(
|
||||||
|
"App hiding failed",
|
||||||
|
AppMigration.patchAndHide(
|
||||||
|
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
||||||
|
label = "Settings",
|
||||||
|
pkg = "repackaged.$APP_PACKAGE_NAME"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setupAppRestore() {
|
||||||
|
runBlocking {
|
||||||
|
assertTrue(
|
||||||
|
"App restoration failed",
|
||||||
|
AppMigration.restoreApp(
|
||||||
|
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.topjohnwu.magisk.test
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class MagiskAppTest {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@BeforeClass
|
||||||
|
@JvmStatic
|
||||||
|
fun before() {
|
||||||
|
assertTrue("Should have root access", Shell.getShell().isRoot)
|
||||||
|
// Make sure the root service is running
|
||||||
|
RootUtils.Connection.await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testZygisk() {
|
||||||
|
assertTrue("Zygisk should be enabled", Info.isZygiskEnabled)
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ import dalvik.system.BaseDexClassLoader;
|
|||||||
public class DynamicClassLoader extends BaseDexClassLoader {
|
public class DynamicClassLoader extends BaseDexClassLoader {
|
||||||
|
|
||||||
public DynamicClassLoader(File apk) {
|
public DynamicClassLoader(File apk) {
|
||||||
this(apk, getSystemClassLoader());
|
this(apk, DynamicClassLoader.class.getClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
public DynamicClassLoader(File apk, ClassLoader parent) {
|
public DynamicClassLoader(File apk, ClassLoader parent) {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
</application>
|
</application>
|
||||||
|
|
||||||
<instrumentation
|
<instrumentation
|
||||||
android:name="androidx.test.runner.AndroidJUnitRunner"
|
android:name="com.topjohnwu.magisk.test.TestRunner"
|
||||||
android:targetPackage="com.topjohnwu.magisk"
|
android:targetPackage="com.topjohnwu.magisk"
|
||||||
android:label="Tests for Magisk" />
|
android:label="Tests for Magisk" />
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.topjohnwu.magisk.test
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.runner.AndroidJUnitRunner
|
||||||
|
|
||||||
|
class TestRunner : AndroidJUnitRunner() {
|
||||||
|
override fun onCreate(arguments: Bundle) {
|
||||||
|
// Force using the target context's classloader to run tests
|
||||||
|
arguments.putString("classLoader", TestClassLoader::class.java.name)
|
||||||
|
super.onCreate(arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val targetClassLoader inline get() =
|
||||||
|
InstrumentationRegistry.getInstrumentation().targetContext.classLoader
|
||||||
|
|
||||||
|
class TestClassLoader : ClassLoader(targetClassLoader)
|
@ -80,7 +80,7 @@ void su_info::check_db() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We need to check our manager
|
// We need to check our manager
|
||||||
if (access.log || access.notify) {
|
if (access.policy == QUERY || access.log || access.notify) {
|
||||||
mgr_uid = get_manager(to_user_id(eval_uid), &mgr_pkg, true);
|
mgr_uid = get_manager(to_user_id(eval_uid), &mgr_pkg, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ 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
|
||||||
|
|
||||||
@ -24,14 +23,27 @@ print_error() {
|
|||||||
echo -e "\n\033[41;39m${1}\033[0m\n"
|
echo -e "\n\033[41;39m${1}\033[0m\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_instrument_tests() {
|
# $1 = TestClass#method
|
||||||
local out=$(adb shell am instrument -w \
|
# $2: boolean = isRepackaged
|
||||||
--user 0 \
|
run_instrument_test() {
|
||||||
-e class "$1" \
|
local test_pkg
|
||||||
com.topjohnwu.magisk.test/androidx.test.runner.AndroidJUnitRunner)
|
if [ -n "$2" -a $2 ]; then
|
||||||
|
test_pkg="repackaged.com.topjohnwu.magisk.test"
|
||||||
|
else
|
||||||
|
test_pkg=com.topjohnwu.magisk.test
|
||||||
|
fi
|
||||||
|
local out=$(adb shell am instrument -w --user 0 \
|
||||||
|
-e class "com.topjohnwu.magisk.test.$1" \
|
||||||
|
"$test_pkg/com.topjohnwu.magisk.test.TestRunner")
|
||||||
grep -q 'OK (' <<< "$out"
|
grep -q 'OK (' <<< "$out"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# $1 = pkg
|
||||||
|
wait_for_pm() {
|
||||||
|
sleep 5
|
||||||
|
adb shell pm uninstall $1 || true
|
||||||
|
}
|
||||||
|
|
||||||
test_setup() {
|
test_setup() {
|
||||||
local variant=$1
|
local variant=$1
|
||||||
adb shell 'PATH=$PATH:/debug_ramdisk magisk -v'
|
adb shell 'PATH=$PATH:/debug_ramdisk magisk -v'
|
||||||
@ -43,14 +55,34 @@ test_setup() {
|
|||||||
adb install -r -g out/test-${variant}.apk
|
adb install -r -g out/test-${variant}.apk
|
||||||
|
|
||||||
# Run setup through the test app
|
# Run setup through the test app
|
||||||
run_instrument_tests "$test_pkg.Environment#setupMagisk"
|
run_instrument_test 'Environment#setupMagisk'
|
||||||
}
|
}
|
||||||
|
|
||||||
test_app() {
|
test_app() {
|
||||||
# Run app tests
|
# Run app tests
|
||||||
run_instrument_tests "$test_pkg.MagiskAppTest"
|
run_instrument_test 'MagiskAppTest'
|
||||||
|
|
||||||
# Test shell su request
|
# Test shell su request
|
||||||
run_instrument_tests "$test_pkg.Environment#setupShellGrantTest"
|
run_instrument_test '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'
|
||||||
|
adb shell am force-stop com.topjohnwu.magisk
|
||||||
|
|
||||||
|
# Test app hiding
|
||||||
|
run_instrument_test 'Environment#setupAppHide'
|
||||||
|
wait_for_pm com.topjohnwu.magisk
|
||||||
|
|
||||||
|
# Make sure it still works
|
||||||
|
run_instrument_test 'MagiskAppTest' true
|
||||||
|
|
||||||
|
# Test shell su request
|
||||||
|
run_instrument_test '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
|
||||||
|
run_instrument_test 'Environment#setupAppRestore' true
|
||||||
|
wait_for_pm repackaged.com.topjohnwu.magisk
|
||||||
|
|
||||||
|
# Make sure it still works
|
||||||
|
run_instrument_test 'MagiskAppTest'
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user