Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update instrumentation tests #8653

Merged
merged 4 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
45 changes: 45 additions & 0 deletions app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.topjohnwu.magisk.test

import android.os.ParcelFileDescriptor.AutoCloseInputStream
import androidx.annotation.Keep
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Assume.assumeTrue
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 : BaseTest {

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

@After
fun teardown() {
device.pressHome()
}

@Test
fun testLaunchLsposedManager() {
assumeTrue(Environment.lsposed())

uiAutomation.executeShellCommand(
"am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity"
).let { pfd -> AutoCloseInputStream(pfd).use { it.readBytes() } }

val pattern = Pattern.compile("$LSPOSED_PKG:id/.*")
assertNotNull(
"LSPosed manager launch failed",
device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10))
)
}
}
26 changes: 26 additions & 0 deletions app/core/src/main/java/com/topjohnwu/magisk/test/BaseTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.topjohnwu.magisk.test

import android.app.Instrumentation
import android.app.UiAutomation
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import org.junit.Assert.assertTrue

interface BaseTest {
val instrumentation: Instrumentation
get() = InstrumentationRegistry.getInstrumentation()
val context: Context get() = instrumentation.targetContext
val uiAutomation: UiAutomation get() = instrumentation.uiAutomation
val device: UiDevice get() = UiDevice.getInstance(instrumentation)

companion object {
fun prerequisite() {
assertTrue("Should have root access", Shell.getShell().isRoot)
// Make sure the root service is running
RootUtils.Connection.await()
}
}
}
67 changes: 41 additions & 26 deletions app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt
Original file line number Diff line number Diff line change
@@ -1,61 +1,78 @@
package com.topjohnwu.magisk.test

import android.app.Notification
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.model.su.SuPolicy
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.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.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import timber.log.Timber

@Keep
@RunWith(AndroidJUnit4::class)
class Environment {
class Environment : BaseTest {

companion object {
@BeforeClass
@JvmStatic
fun before() = MagiskAppTest.before()
fun before() = BaseTest.prerequisite()

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

@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 setupShellGrantTest() {
fun setupLsposed() {
assumeTrue(lsposed())

val notify = object : DownloadNotifier {
override val context = this@Environment.context
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {}
}
val processor = DownloadProcessor(notify)
val zip = context.cachedFile("lsposed.zip")
runBlocking {
// Inject an undetermined + mute logging policy for ADB shell
val policy = SuPolicy(
uid = 2000,
logging = false,
notification = false,
until = 0L
ServiceLocator.networkService.fetchFile(LSPOSED_URL).byteStream().use {
processor.handleModule(it, zip.toUri())
}
assertTrue(
"LSPosed installation failed",
FlashZip(zip.toUri(), TimberLog, TimberLog).exec()
)
ServiceLocator.policyDB.update(policy)
// Bypass the need to actually show a dialog
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
Config.prefs.edit().commit()
}
}

Expand All @@ -65,7 +82,7 @@ class Environment {
assertTrue(
"App hiding failed",
AppMigration.patchAndHide(
context = InstrumentationRegistry.getInstrumentation().targetContext,
context = context,
label = "Settings",
pkg = "repackaged.$APP_PACKAGE_NAME"
)
Expand All @@ -78,9 +95,7 @@ class Environment {
runBlocking {
assertTrue(
"App restoration failed",
AppMigration.restoreApp(
context = InstrumentationRegistry.getInstrumentation().targetContext
)
AppMigration.restoreApp(context)
)
}
}
Expand Down
71 changes: 63 additions & 8 deletions app/core/src/main/java/com/topjohnwu/magisk/test/MagiskAppTest.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,86 @@
package com.topjohnwu.magisk.test

import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.ParcelFileDescriptor.AutoCloseInputStream
import androidx.annotation.Keep
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.model.su.SuPolicy
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.TimeUnit

@Keep
@RunWith(AndroidJUnit4::class)
class MagiskAppTest {
class MagiskAppTest : BaseTest {

companion object {
@BeforeClass
@JvmStatic
fun before() {
assertTrue("Should have root access", Shell.getShell().isRoot)
// Make sure the root service is running
RootUtils.Connection.await()
}
fun before() = BaseTest.prerequisite()
}

@Test
fun testZygisk() {
assertTrue("Zygisk should be enabled", Info.isZygiskEnabled)
}

@Test
fun testSuRequest() {
// Bypass the need to actually show a dialog
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
Config.prefs.edit().commit()

// Inject an undetermined + mute logging policy for ADB shell
val policy = SuPolicy(
uid = 2000,
logging = false,
notification = false,
until = 0L
)
runBlocking {
ServiceLocator.policyDB.update(policy)
}

val filter = IntentFilter(Intent.ACTION_VIEW)
filter.addCategory(Intent.CATEGORY_DEFAULT)
val monitor = instrumentation.addMonitor(filter, null, false)

// Try to call su from ADB shell
val cmd = if (Build.VERSION.SDK_INT < 24) {
// API 23 runs executeShellCommand as root
"/system/xbin/su 2000 su -c id"
} else {
"su -c id"
}
val pfd = uiAutomation.executeShellCommand(cmd)

// Make sure SuRequestActivity is launched
val suRequest = monitor.waitForActivityWithTimeout(TimeUnit.SECONDS.toMillis(10))
assertNotNull("SuRequestActivity is not launched", suRequest)

// Check that the request went through
AutoCloseInputStream(pfd).reader().use {
assertTrue(
"Cannot grant root permission from shell",
it.readText().contains("uid=0")
)
}

// Check that the database is updated
runBlocking {
val policy = ServiceLocator.policyDB.fetch(2000)
?: throw AssertionError("PolicyDB is invalid")
assertEquals("Policy for shell is incorrect", SuPolicy.ALLOW, policy.policy)
}
}
}
1 change: 1 addition & 0 deletions app/test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ dependencies {
implementation(libs.test.runner)
implementation(libs.test.rules)
implementation(libs.test.junit)
implementation(libs.test.uiautomator)
}
13 changes: 11 additions & 2 deletions app/test/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />

<queries tools:node="removeAll" />

<application tools:node="replace">
<uses-library android:name="android.test.runner" />
</application>

<instrumentation
android:name="com.topjohnwu.magisk.test.AppTestRunner"
android:targetPackage="com.topjohnwu.magisk" />

<instrumentation
android:name="com.topjohnwu.magisk.test.TestRunner"
android:targetPackage="com.topjohnwu.magisk"
android:label="Tests for Magisk" />
android:targetPackage="com.topjohnwu.magisk.test" />

</manifest>
Loading