From 861f263f06a21363045f9a878013e194bacdd124 Mon Sep 17 00:00:00 2001 From: Winson Date: Wed, 18 Mar 2020 11:00:49 -0700 Subject: [PATCH] Allow any actor when target package is debuggable Also adds comprehensive tests for ActorState in overlayable actor enforcement. These tests work by mirroring the logic branches in the actual method, but using test data instead. This technically doesn't run every combination of parameters, but that would be a massive set. This branching approach should be sufficient. It has fully replaced the previous set of manually written test cases. Bug: 150390069 Test: atest com.android.server.om.OverlayActorEnforcerTests Test: atest OverlayRemountedTest Change-Id: Iabf8b93307ee51ecced41335fdf47eb59c15c365 --- .../server/om/OverlayActorEnforcer.java | 45 +- .../server/om/OverlayActorEnforcerTests.kt | 503 +++++++++++++----- .../android/server/testutils/MockitoUtils.kt | 5 +- 3 files changed, 392 insertions(+), 161 deletions(-) diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index 8c03c6ce3092b..8121a43e90601 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -26,6 +26,7 @@ import android.os.Process; import android.text.TextUtils; import android.util.Pair; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -66,7 +67,7 @@ public class OverlayActorEnforcer { String actorNamespace = actorUri.getAuthority(); Map namespace = namedActors.get(actorNamespace); - if (namespace == null) { + if (ArrayUtils.isEmpty(namespace)) { return Pair.create(null, ActorState.MISSING_NAMESPACE); } @@ -102,21 +103,32 @@ public class OverlayActorEnforcer { * See {@link OverlayActorEnforcer} class comment for actor requirements. * @return true if the actor is allowed to act on the target overlayInfo */ - private ActorState isAllowedActor(String methodName, OverlayInfo overlayInfo, + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public ActorState isAllowedActor(String methodName, OverlayInfo overlayInfo, int callingUid, int userId) { + // Checked first to avoid package not found errors, which are ignored for calls from shell switch (callingUid) { case Process.ROOT_UID: case Process.SYSTEM_UID: return ActorState.ALLOWED; } + final String targetPackageName = overlayInfo.targetPackageName; + final PackageInfo targetPkgInfo = mPackageManager.getPackageInfo(targetPackageName, userId); + if (targetPkgInfo == null) { + return ActorState.TARGET_NOT_FOUND; + } + + if ((targetPkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + return ActorState.ALLOWED; + } + String[] callingPackageNames = mPackageManager.getPackagesForUid(callingUid); if (ArrayUtils.isEmpty(callingPackageNames)) { return ActorState.NO_PACKAGES_FOR_UID; } // A target is always an allowed actor for itself - String targetPackageName = overlayInfo.targetPackageName; if (ArrayUtils.contains(callingPackageNames, targetPackageName)) { return ActorState.ALLOWED; } @@ -149,7 +161,7 @@ public class OverlayActorEnforcer { targetOverlayable = mPackageManager.getOverlayableForTarget(targetPackageName, targetOverlayableName, userId); } catch (IOException e) { - return ActorState.UNABLE_TO_GET_TARGET; + return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE; } if (targetOverlayable == null) { @@ -189,7 +201,7 @@ public class OverlayActorEnforcer { } // Currently only pre-installed apps can be actors - if (!appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) { + if (!appInfo.isSystemApp()) { return ActorState.ACTOR_NOT_PREINSTALLED; } @@ -203,22 +215,25 @@ public class OverlayActorEnforcer { /** * For easier logging/debugging, a set of all possible failure/success states when running * enforcement. + * + * The ordering of this enum should be maintained in the order that cases are checked in code, + * as this ordering is used inside OverlayActorEnforcerTests. */ public enum ActorState { - ALLOWED, - INVALID_ACTOR, - MISSING_NAMESPACE, - MISSING_PACKAGE, - MISSING_APP_INFO, - ACTOR_NOT_PREINSTALLED, + TARGET_NOT_FOUND, NO_PACKAGES_FOR_UID, - MISSING_ACTOR_NAME, - ERROR_READING_OVERLAYABLE, MISSING_TARGET_OVERLAYABLE_NAME, + MISSING_LEGACY_PERMISSION, + ERROR_READING_OVERLAYABLE, + UNABLE_TO_GET_TARGET_OVERLAYABLE, MISSING_OVERLAYABLE, INVALID_OVERLAYABLE_ACTOR_NAME, NO_NAMED_ACTORS, - UNABLE_TO_GET_TARGET, - MISSING_LEGACY_PERMISSION + MISSING_NAMESPACE, + MISSING_ACTOR_NAME, + MISSING_APP_INFO, + ACTOR_NOT_PREINSTALLED, + INVALID_ACTOR, + ALLOWED } } diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt index 08392737350af..154d42ce3f8d5 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt +++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt @@ -21,172 +21,387 @@ import android.content.om.OverlayableInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.os.Process -import org.junit.Rule +import com.android.server.om.OverlayActorEnforcer.ActorState +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.BeforeClass import org.junit.Test -import org.junit.rules.ExpectedException -import java.lang.UnsupportedOperationException +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mockito.spy +import java.io.IOException +@RunWith(Parameterized::class) class OverlayActorEnforcerTests { + companion object { - private const val NAMESPACE = "testnamespace" - private const val ACTOR_NAME = "testactor" - private const val ACTOR_PKG_NAME = "com.test.actor.one" + private const val TARGET_PKG = "com.test.target" + private const val OVERLAY_PKG = "com.test.overlay" + + private const val VALID_NAMESPACE = "testNamespaceValid" + private const val INVALID_NAMESPACE = "testNamespaceInvalid" + private const val VALID_ACTOR_NAME = "testActorOne" + private const val INVALID_ACTOR_NAME = "testActorTwo" + private const val VALID_ACTOR_PKG = "com.test.actor.valid" + private const val INVALID_ACTOR_PKG = "com.test.actor.invalid" private const val OVERLAYABLE_NAME = "TestOverlayable" - private const val UID = 3536 + private const val NULL_UID = 3536 + private const val EMPTY_UID = NULL_UID + 1 + private const val INVALID_ACTOR_UID = NULL_UID + 2 + private const val VALID_ACTOR_UID = NULL_UID + 3 + private const val TARGET_UID = NULL_UID + 4 private const val USER_ID = 55 - } - @get:Rule - val expectedException = ExpectedException.none()!! + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun parameters() = CASES.mapIndexed { caseIndex, testCase -> + fun param(pair: Pair Unit>, type: Params.Type): Params { + val expectedState = testCase.state.takeUnless { type == Params.Type.ALLOWED } + ?: ActorState.ALLOWED + val (caseName, case) = pair + val testName = makeTestName(testCase, caseName, type) + return Params(caseIndex, expectedState, testName, type, case) + } - @Test - fun isRoot() { - verify(callingUid = Process.ROOT_UID) - } + testCase.failures.map { param(it, Params.Type.FAILURE) } + + testCase.allowed.map { param(it, Params.Type.ALLOWED) } + }.flatten() - @Test(expected = SecurityException::class) - fun isShell() { - verify(callingUid = Process.SHELL_UID) - } - - @Test - fun isSystem() { - verify(callingUid = Process.SYSTEM_UID) - } - - @Test(expected = SecurityException::class) - fun noOverlayable_noTarget() { - verify(targetOverlayableName = null) - } - - @Test - fun noOverlayable_noTarget_withPermission() { - verify(targetOverlayableName = null, hasPermission = true) - } - - @Test(expected = SecurityException::class) - fun noOverlayable_withTarget() { - verify(targetOverlayableName = OVERLAYABLE_NAME) - } - - @Test(expected = SecurityException::class) - fun withOverlayable_noTarget() { - verify( - targetOverlayableName = null, - overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, null) - ) - } - - @Test(expected = SecurityException::class) - fun withOverlayable_noActor() { - verify( - overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, null) - ) - } - - @Test - fun withOverlayable_noActor_withPermission() { - verify( - hasPermission = true, - overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, null) - ) - } - - @Test(expected = SecurityException::class) - fun withOverlayable_withActor_notActor() { - verify( - isActor = false, - overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, - "overlay://$NAMESPACE/$ACTOR_NAME") - ) - } - - @Test(expected = SecurityException::class) - fun withOverlayable_withActor_isActor_notPreInstalled() { - verify( - isActor = true, - isPreInstalled = false, - overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, - "overlay://$NAMESPACE/$ACTOR_NAME") - ) - } - - @Test - fun withOverlayable_withActor_isActor_isPreInstalled() { - verify( - isActor = true, - isPreInstalled = true, - overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, - "overlay://$NAMESPACE/$ACTOR_NAME") - ) - } - - @Test(expected = SecurityException::class) - fun withOverlayable_invalidActor() { - verify( - isActor = true, - isPreInstalled = true, - overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, "notValidActor") - ) - } - - private fun verify( - isActor: Boolean = false, - isPreInstalled: Boolean = false, - hasPermission: Boolean = false, - overlayableInfo: OverlayableInfo? = null, - callingUid: Int = UID, - targetOverlayableName: String? = OVERLAYABLE_NAME - ) { - val callback = MockCallback( - isActor = isActor, - isPreInstalled = isPreInstalled, - hasPermission = hasPermission, - overlayableInfo = overlayableInfo - ) - - val overlayInfo = overlayInfo(targetOverlayableName) - OverlayActorEnforcer(callback) - .enforceActor(overlayInfo, "test", callingUid, USER_ID) - } - - private fun overlayInfo(targetOverlayableName: String?) = OverlayInfo("com.test.overlay", - "com.test.target", targetOverlayableName, null, "/path", OverlayInfo.STATE_UNKNOWN, 0, - 0, false) - - private class MockCallback( - private val isActor: Boolean = false, - private val isPreInstalled: Boolean = false, - private val hasPermission: Boolean = false, - private val overlayableInfo: OverlayableInfo? = null, - private vararg val packageNames: String = arrayOf("com.test.actor.one") - ) : PackageManagerHelper { - - override fun getNamedActors() = if (isActor) { - mapOf(NAMESPACE to mapOf(ACTOR_NAME to ACTOR_PKG_NAME)) - } else { - emptyMap() + @BeforeClass + @JvmStatic + fun checkAllCasesHandled() { + // Assert that all states have been tested at least once. + assertThat(CASES.map { it.state }.distinct()).containsAllIn(ActorState.values()) } + @BeforeClass + @JvmStatic + fun checkAllCasesUniquelyNamed() { + val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase -> + testCase.failures.map { + makeTestName(testCase, it.first, Params.Type.FAILURE) + } + testCase.allowed.map { + makeTestName(testCase, it.first, Params.Type.ALLOWED) + } + } + .flatten() + .groupingBy { it } + .eachCount() + .filterValues { it > 1 } + .keys + + assertThat(duplicateCaseNames).isEmpty() + } + + /* + The pattern in this block is a result of the incredible number of branches in + enforcement logic. It serves to verify failures with the assumption that all errors + are checked in order. The idea is to emulate the if-else branches from code, but using + actual test data instead of if statements. + + Each state is verified by providing a failure or exclusive set of failures which cause + a failure state to be returned. Each state also provides a success case which will + "skip" the state. This allows subsequent failure cases to cascade from the first case + by calling all the skip branches for preceding states and then choosing only 1 of + the failures to test. + + Given the failure states A, B, and C: testA calls A.failure + assert, testB calls + A.skip + B.failure + assert, testC calls A.skip + B.skip + C.failure + assert, etc. + + Calling `allowed` is a special case for when there is a combination of parameters that + skips the remaining checks and immediately allows the actor through. For these cases, + the first failure branch will be run, assert that it's not allowed, and the + allowed branch will run, asserting that it now results in ALLOWED, skipping all + remaining functions. + + This is an ordered list of TestCase objects, with the possibility to repeat failure + states if any can occur multiple times in the logic tree. + + Each failure must be handled at least once. + */ + private val CASES = listOf( + ActorState.TARGET_NOT_FOUND withCases { + failure("nullPkgInfo") { targetPkgInfo = null } + allowed("debuggable") { + targetPkgInfo = pkgInfo(TARGET_PKG).apply { + applicationInfo.flags = ApplicationInfo.FLAG_DEBUGGABLE + } + } + skip { targetPkgInfo = pkgInfo(TARGET_PKG) } + }, + ActorState.NO_PACKAGES_FOR_UID withCases { + failure("empty") { callingUid = EMPTY_UID } + failure("null") { callingUid = NULL_UID } + failure("shell") { callingUid = Process.SHELL_UID } + allowed("targetUid") { callingUid = TARGET_UID } + allowed("rootUid") { callingUid = Process.ROOT_UID } + allowed("systemUid") { callingUid = Process.SYSTEM_UID } + skip { callingUid = INVALID_ACTOR_UID } + }, + ActorState.MISSING_TARGET_OVERLAYABLE_NAME withCases { + failure("nullTargetOverlayableName") { + overlayInfoParams.targetOverlayableName = null + targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, + "overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME") + } + skip { overlayInfoParams.targetOverlayableName = OVERLAYABLE_NAME } + }, + ActorState.MISSING_LEGACY_PERMISSION withCases { + failure("noPermission") { + overlayInfoParams.targetOverlayableName = null + targetOverlayableInfo = null + hasPermission = false + } + allowed("hasPermission") { hasPermission = true } + skip { overlayInfoParams.targetOverlayableName = OVERLAYABLE_NAME } + }, + ActorState.ERROR_READING_OVERLAYABLE withCases { + failure("doesTargetDefineOverlayableIOException") { + overlayInfoParams.targetOverlayableName = null + whenever(doesTargetDefineOverlayable(TARGET_PKG, USER_ID)) + .thenThrow(IOException::class.java) + } + skip { overlayInfoParams.targetOverlayableName = OVERLAYABLE_NAME } + }, + ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE withCases { + failure("getOverlayableForTargetIOException") { + whenever(getOverlayableForTarget(TARGET_PKG, OVERLAYABLE_NAME, + USER_ID)).thenThrow(IOException::class.java) + } + }, + ActorState.MISSING_OVERLAYABLE withCases { + failure("nullTargetOverlayableInfo") { targetOverlayableInfo = null } + skip { + targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, + "overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME") + } + }, + ActorState.MISSING_LEGACY_PERMISSION withCases { + failure("noPermissionNullActor") { + targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, null) + hasPermission = false + } + failure("noPermissionEmptyActor") { + targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, "") + hasPermission = false + } + allowed("hasPermissionNullActor") { + hasPermission = true + } + skip { + targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, + "overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME") + } + }, + ActorState.INVALID_OVERLAYABLE_ACTOR_NAME withCases { + fun TestState.mockActor(actorUri: String) { + targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, actorUri) + } + failure("wrongScheme") { + mockActor("notoverlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME") + } + failure("extraPath") { + mockActor("overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME/extraPath") + } + failure("missingPath") { mockActor("overlay://$VALID_NAMESPACE") } + failure("missingAuthority") { mockActor("overlay://") } + skip { mockActor("overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME") } + }, + ActorState.NO_NAMED_ACTORS withCases { + failure("empty") { namedActorsMap = emptyMap() } + skip { + namedActorsMap = mapOf(INVALID_NAMESPACE to + mapOf(INVALID_ACTOR_NAME to VALID_ACTOR_PKG)) + } + }, + ActorState.MISSING_NAMESPACE withCases { + failure("invalidNamespace") { + namedActorsMap = mapOf(INVALID_NAMESPACE to + mapOf(INVALID_ACTOR_NAME to VALID_ACTOR_PKG)) + } + skip { + namedActorsMap = mapOf(VALID_NAMESPACE to + mapOf(INVALID_ACTOR_NAME to VALID_ACTOR_PKG)) + } + }, + ActorState.MISSING_ACTOR_NAME withCases { + failure("invalidActorName") { + namedActorsMap = mapOf(VALID_NAMESPACE to + mapOf(INVALID_ACTOR_NAME to VALID_ACTOR_PKG)) + } + skip { + namedActorsMap = mapOf(VALID_NAMESPACE to + mapOf(VALID_ACTOR_NAME to VALID_ACTOR_PKG)) + } + }, + ActorState.MISSING_APP_INFO withCases { + failure("nullActorPkgInfo") { actorPkgInfo = null } + failure("nullActorAppInfo") { + actorPkgInfo = PackageInfo().apply { applicationInfo = null } + } + skip { actorPkgInfo = pkgInfo(VALID_ACTOR_PKG) } + }, + ActorState.ACTOR_NOT_PREINSTALLED withCases { + failure("notSystem") { + actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply { + applicationInfo.flags = 0 + } + } + skip { + actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply { + applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM + } + } + }, + ActorState.INVALID_ACTOR withCases { + failure("invalidUid") { callingUid = INVALID_ACTOR_UID } + skip { callingUid = VALID_ACTOR_UID } + }, + ActorState.ALLOWED withCases { + // No point making an exception for this case in all of the test code, so + // just pretend this is a failure that results in a success result code. + failure("allowed") { /* Do nothing */ } + } + ) + + data class OverlayInfoParams( + var targetPackageName: String = TARGET_PKG, + var targetOverlayableName: String? = null + ) { + fun toOverlayInfo() = OverlayInfo( + OVERLAY_PKG, + targetPackageName, + targetOverlayableName, + null, + "/path", + OverlayInfo.STATE_UNKNOWN, 0, + 0, false) + } + + private infix fun ActorState.withCases(block: TestCase.() -> Unit) = + TestCase(this).apply(block) + + private fun pkgInfo(pkgName: String): PackageInfo = mockThrowOnUnmocked { + this.packageName = pkgName + this.applicationInfo = ApplicationInfo().apply { + this.packageName = pkgName + } + } + + private fun makeTestName(testCase: TestCase, caseName: String, type: Params.Type): String { + val resultSuffix = if (type == Params.Type.ALLOWED) "allowed" else "failed" + return "${testCase.state}_${resultSuffix}_$caseName" + } + } + + @Parameterized.Parameter(0) + lateinit var params: Params + + @Test + fun verify() { + // Apply all the skip states before the failure to be verified + val testState = CASES.take(params.index) + .fold(TestState.create()) { testState, case -> + testState.apply(case.skip) + } + + // If testing an allowed branch, first apply a failure to ensure it fails + if (params.type == Params.Type.ALLOWED) { + CASES[params.index].failures.firstOrNull()?.second?.run(testState::apply) + assertThat(testState.toResult()).isNotEqualTo(ActorState.ALLOWED) + } + + // Apply the test case in the params to the collected state + testState.apply(params.function) + + // Assert the result matches the expected state + assertThat(testState.toResult()).isEqualTo(params.expectedState) + } + + private fun TestState.toResult() = OverlayActorEnforcer(this) + .isAllowedActor("test", overlayInfoParams.toOverlayInfo(), callingUid, USER_ID) + + data class Params( + var index: Int, + var expectedState: ActorState, + val testName: String, + val type: Type, + val function: TestState.() -> Unit + ) { + override fun toString() = testName + + enum class Type { + FAILURE, + ALLOWED + } + } + + data class TestCase( + val state: ActorState, + val failures: MutableList Unit>> = mutableListOf(), + var allowed: MutableList Unit>> = mutableListOf(), + var skip: (TestState.() -> Unit) = {} + ) { + fun failure(caseName: String, block: TestState.() -> Unit) { + failures.add(caseName to block) + } + + fun allowed(caseName: String, block: TestState.() -> Unit) { + allowed.add(caseName to block) + } + + fun skip(block: TestState.() -> Unit) { + this.skip = block + } + } + + open class TestState private constructor( + var callingUid: Int = NULL_UID, + val overlayInfoParams: OverlayInfoParams = OverlayInfoParams(), + var namedActorsMap: Map> = emptyMap(), + var hasPermission: Boolean = false, + var targetOverlayableInfo: OverlayableInfo? = null, + var targetPkgInfo: PackageInfo? = null, + var actorPkgInfo: PackageInfo? = null, + vararg val packageNames: String = arrayOf("com.test.actor.one") + ) : PackageManagerHelper { + + companion object { + // Enforce that new instances are spied + fun create() = spy(TestState())!! + } + + override fun getNamedActors() = namedActorsMap + + @Throws(IOException::class) override fun getOverlayableForTarget( packageName: String, targetOverlayableName: String, userId: Int - ) = overlayableInfo + ) = targetOverlayableInfo?.takeIf { + // Protect against this method being called with the wrong package name + targetPkgInfo == null || targetPkgInfo?.packageName == packageName + } override fun getPackagesForUid(uid: Int) = when (uid) { - UID -> packageNames + EMPTY_UID -> emptyArray() + INVALID_ACTOR_UID -> arrayOf(INVALID_ACTOR_PKG) + VALID_ACTOR_UID -> arrayOf(VALID_ACTOR_PKG) + TARGET_UID -> arrayOf(TARGET_PKG) + NULL_UID -> null else -> null } - override fun getPackageInfo(packageName: String, userId: Int) = PackageInfo().apply { - applicationInfo = ApplicationInfo().apply { - flags = if (isPreInstalled) ApplicationInfo.FLAG_SYSTEM else 0 - } - } + override fun getPackageInfo(packageName: String, userId: Int) = + listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName } + @Throws(IOException::class) // Mockito requires this checked exception to be declared override fun doesTargetDefineOverlayable(targetPackageName: String?, userId: Int): Boolean { - return overlayableInfo != null + return targetOverlayableInfo?.takeIf { + // Protect against this method being called with the wrong package name + targetPkgInfo == null || targetPkgInfo?.packageName == targetPackageName + } != null } override fun enforcePermission(permission: String?, message: String?) { diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt index 056fa886f6402..c50973dc019e2 100644 --- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt +++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt @@ -64,7 +64,7 @@ fun whenever(mock: Type, block: InvocationOnMock.() -> Any?) = fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { } -inline fun spyThrowOnUnmocked(value: T?, block: T.() -> Unit): T { +inline fun spyThrowOnUnmocked(value: T?, block: T.() -> Unit = {}): T { val swappingAnswer = object : Answer { var delegate: Answer<*> = Answers.RETURNS_DEFAULTS @@ -81,4 +81,5 @@ inline fun spyThrowOnUnmocked(value: T?, block: T.() -> Unit): T { } } -inline fun mockThrowOnUnmocked(block: T.() -> Unit) = spyThrowOnUnmocked(null, block) +inline fun mockThrowOnUnmocked(block: T.() -> Unit = {}) = + spyThrowOnUnmocked(null, block)