Add base classes for common ConnectivityService tests.

This sets up what is necessary for an instrumented
ConnectivityService to run. Users of this class are
meant to inherit CSTest.

This is still relatively basic and does not have all the
instrumentation in ConnectivityServiceTest. Developers
looking to extend CSTest may find some instrumentation
missing ; when they add the missing instrumentation,
they should consider whether it should be generic for all
CSTests (and put it in base/), or whether it's local to
their own test suite. This should enable faster testing
as each CSTest children will only need to set up the
instrumentation it actually needs.

This patch also migrates a basic test to have a first user.

Bug: 272685721
Test: ConnectivityServiceTest
      CSBasicMethodsTest
Change-Id: I1c47f616af90629c9cb2a6ae89d992b19863e704
This commit is contained in:
Chalard Jean
2023-08-22 19:06:12 +09:00
parent 8df4d76aa2
commit 0f5c4fe9cb
6 changed files with 579 additions and 20 deletions

View File

@@ -64,6 +64,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer; import java.util.function.Consumer;
public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
private final NetworkCapabilities mNetworkCapabilities; private final NetworkCapabilities mNetworkCapabilities;
private final HandlerThread mHandlerThread; private final HandlerThread mHandlerThread;
private final Context mContext; private final Context mContext;
@@ -468,4 +471,8 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
public boolean isBypassableVpn() { public boolean isBypassableVpn() {
return mNetworkAgentConfig.isBypassableVpn(); return mNetworkAgentConfig.isBypassableVpn();
} }
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
} }

View File

@@ -75,10 +75,7 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPR
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
@@ -492,6 +489,8 @@ import java.util.stream.Collectors;
* Build, install and run with: * Build, install and run with:
* runtest frameworks-net -c com.android.server.ConnectivityServiceTest * runtest frameworks-net -c com.android.server.ConnectivityServiceTest
*/ */
// TODO : move methods from this test to smaller tests in the 'connectivityservice' directory
// to enable faster testing of smaller groups of functionality.
@RunWith(DevSdkIgnoreRunner.class) @RunWith(DevSdkIgnoreRunner.class)
@SmallTest @SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
@@ -1016,6 +1015,9 @@ public class ConnectivityServiceTest {
} }
private class TestNetworkAgentWrapper extends NetworkAgentWrapper { private class TestNetworkAgentWrapper extends NetworkAgentWrapper {
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
private static final int VALIDATION_RESULT_INVALID = 0; private static final int VALIDATION_RESULT_INVALID = 0;
private static final long DATA_STALL_TIMESTAMP = 10L; private static final long DATA_STALL_TIMESTAMP = 10L;
@@ -1340,6 +1342,9 @@ public class ConnectivityServiceTest {
* operations have been processed and test for them. * operations have been processed and test for them.
*/ */
private static class MockNetworkFactory extends NetworkFactory { private static class MockNetworkFactory extends NetworkFactory {
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSTest and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false); private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
static class RequestEntry { static class RequestEntry {
@@ -1476,6 +1481,10 @@ public class ConnectivityServiceTest {
} }
private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork { private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork {
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSTest and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
// Careful ! This is different from mNetworkAgent, because MockNetworkAgent does // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
// not inherit from NetworkAgent. // not inherit from NetworkAgent.
private TestNetworkAgentWrapper mMockNetworkAgent; private TestNetworkAgentWrapper mMockNetworkAgent;
@@ -1852,6 +1861,9 @@ public class ConnectivityServiceTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSTest and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
doReturn(asList(PRIMARY_USER_INFO)).when(mUserManager).getAliveUsers(); doReturn(asList(PRIMARY_USER_INFO)).when(mUserManager).getAliveUsers();
doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean()); doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());
doReturn(PRIMARY_USER_INFO).when(mUserManager).getUserInfo(PRIMARY_USER); doReturn(PRIMARY_USER_INFO).when(mUserManager).getUserInfo(PRIMARY_USER);
@@ -1938,6 +1950,9 @@ public class ConnectivityServiceTest {
setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT); setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
setAlwaysOnNetworks(false); setAlwaysOnNetworks(false);
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSTest and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
} }
private void initMockedResources() { private void initMockedResources() {
@@ -1974,6 +1989,9 @@ public class ConnectivityServiceTest {
final ConnectivityResources mConnRes; final ConnectivityResources mConnRes;
final ArraySet<Pair<Long, Integer>> mEnabledChangeIds = new ArraySet<>(); final ArraySet<Pair<Long, Integer>> mEnabledChangeIds = new ArraySet<>();
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSTest and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
ConnectivityServiceDependencies(final Context mockResContext) { ConnectivityServiceDependencies(final Context mockResContext) {
mConnRes = new ConnectivityResources(mockResContext); mConnRes = new ConnectivityResources(mockResContext);
} }
@@ -2582,23 +2600,6 @@ public class ConnectivityServiceTest {
return true; return true;
} }
@Test
public void testNetworkTypes() {
// Ensure that our mocks for the networkAttributes config variable work as expected. If they
// don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
// will fail. Failing here is much easier to debug.
assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
assertFalse(mCm.isNetworkSupported(TYPE_PROXY));
// Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
// mocks, this assert exercises the ConnectivityService code path that ensures that
// TYPE_ETHERNET is supported if the ethernet service is running.
assertTrue(mCm.isNetworkSupported(TYPE_ETHERNET));
}
@Test @Test
public void testNetworkFeature() throws Exception { public void testNetworkFeature() throws Exception {
// Connect the cell agent and wait for the connected broadcast. // Connect the cell agent and wait for the connected broadcast.
@@ -18801,4 +18802,7 @@ public class ConnectivityServiceTest {
verifyClatdStop(null /* inOrder */, MOBILE_IFNAME); verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
} }
// Note : adding tests is ConnectivityServiceTest is deprecated, as it is too big for
// maintenance. Please consider adding new tests in subclasses of CSTest instead.
} }

View File

@@ -0,0 +1,35 @@
@file:Suppress("DEPRECATION") // This file tests a bunch of deprecated methods : don't warn about it
package com.android.server
import android.net.ConnectivityManager
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@IgnoreUpTo(Build.VERSION_CODES.R)
class CSBasicMethodsTest : CSTest() {
@Test
fun testNetworkTypes() {
// Ensure that mocks for the networkAttributes config variable work as expected. If they
// don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
// will fail. Failing here is much easier to debug.
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_WIFI))
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE))
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE_MMS))
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE_FOTA))
assertFalse(cm.isNetworkSupported(ConnectivityManager.TYPE_PROXY))
// Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
// mocks, this assert exercises the ConnectivityService code path that ensures that
// TYPE_ETHERNET is supported if the ethernet service is running.
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_ETHERNET))
}
}

View File

@@ -0,0 +1,134 @@
package com.android.server
import android.content.Context
import android.net.ConnectivityManager
import android.net.INetworkMonitor
import android.net.INetworkMonitorCallbacks
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkAgent
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkProvider
import android.net.NetworkRequest
import android.net.NetworkScore
import android.net.NetworkTestResultParcelable
import android.net.networkstack.NetworkStackClientBase
import android.os.HandlerThread
import com.android.modules.utils.build.SdkLevel
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.TestableNetworkCallback
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.verify
import org.mockito.stubbing.Answer
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.fail
private inline fun <reified T> ArgumentCaptor() = ArgumentCaptor.forClass(T::class.java)
private val agentCounter = AtomicInteger(1)
private fun nextAgentId() = agentCounter.getAndIncrement()
/**
* A wrapper for network agents, for use with CSTest.
*
* This class knows how to interact with CSTest and has helpful methods to make fake agents
* that can be manipulated directly from a test.
*/
class CSAgentWrapper(
val context: Context,
csHandlerThread: HandlerThread,
networkStack: NetworkStackClientBase,
nac: NetworkAgentConfig,
val nc: NetworkCapabilities,
val lp: LinkProperties,
val score: FromS<NetworkScore>,
val provider: NetworkProvider?
) : TestableNetworkCallback.HasNetwork {
private val TAG = "CSAgent${nextAgentId()}"
private val VALIDATION_RESULT_INVALID = 0
private val VALIDATION_TIMESTAMP = 1234L
private val agent: NetworkAgent
private val nmCallbacks: INetworkMonitorCallbacks
val networkMonitor = mock<INetworkMonitor>()
override val network: Network get() = agent.network!!
init {
// Capture network monitor callbacks and simulate network monitor
val validateAnswer = Answer {
CSTest.CSTestExecutor.execute { onValidationRequested() }
null
}
doAnswer(validateAnswer).`when`(networkMonitor).notifyNetworkConnected(any(), any())
doAnswer(validateAnswer).`when`(networkMonitor).notifyNetworkConnectedParcel(any())
doAnswer(validateAnswer).`when`(networkMonitor).forceReevaluation(anyInt())
val nmNetworkCaptor = ArgumentCaptor<Network>()
val nmCbCaptor = ArgumentCaptor<INetworkMonitorCallbacks>()
doNothing().`when`(networkStack).makeNetworkMonitor(
nmNetworkCaptor.capture(),
any() /* name */,
nmCbCaptor.capture())
// Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass.
if (SdkLevel.isAtLeastS()) {
agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
nc, lp, score.value, nac, provider) {}
} else {
agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
nc, lp, 50 /* score */, nac, provider) {}
}
agent.register()
assertEquals(agent.network!!.netId, nmNetworkCaptor.value.netId)
nmCallbacks = nmCbCaptor.value
nmCallbacks.onNetworkMonitorCreated(networkMonitor)
}
private fun onValidationRequested() {
if (SdkLevel.isAtLeastT()) {
verify(networkMonitor).notifyNetworkConnectedParcel(any())
} else {
verify(networkMonitor).notifyNetworkConnected(any(), any())
}
nmCallbacks.notifyProbeStatusChanged(0 /* completed */, 0 /* succeeded */)
val p = NetworkTestResultParcelable()
p.result = VALIDATION_RESULT_INVALID
p.probesAttempted = 0
p.probesSucceeded = 0
p.redirectUrl = null
p.timestampMillis = VALIDATION_TIMESTAMP
nmCallbacks.notifyNetworkTestedWithExtras(p)
}
fun connect() {
val mgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val request = NetworkRequest.Builder().clearCapabilities()
.addTransportType(nc.transportTypes[0])
.build()
val cb = TestableNetworkCallback()
mgr.registerNetworkCallback(request, cb)
agent.markConnected()
if (null == cb.poll { it is Available && agent.network == it.network }) {
if (!nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) &&
nc.hasTransport(TRANSPORT_CELLULAR)) {
// ConnectivityService adds NOT_SUSPENDED by default to all non-cell agents. An
// agent without NOT_SUSPENDED will not connect, instead going into the SUSPENDED
// state, so this call will not terminate.
// Instead of forcefully adding NOT_SUSPENDED to all agents like older tools did,
// it's better to let the developer manage it as they see fit but help them
// debug if they forget.
fail("Could not connect the agent. Did you forget to add " +
"NET_CAPABILITY_NOT_SUSPENDED ?")
}
fail("Could not connect the agent. Instrumentation failure ?")
}
mgr.unregisterNetworkCallback(cb)
}
}

View File

@@ -0,0 +1,239 @@
package com.android.server
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.content.pm.UserInfo
import android.content.res.Resources
import android.net.ConnectivityManager
import android.net.INetd
import android.net.InetAddresses
import android.net.IpPrefix
import android.net.LinkAddress
import android.net.LinkProperties
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
import android.net.NetworkPolicyManager
import android.net.NetworkProvider
import android.net.NetworkScore
import android.net.PacProxyManager
import android.net.RouteInfo
import android.net.networkstack.NetworkStackClientBase
import android.os.Handler
import android.os.HandlerThread
import android.os.UserHandle
import android.os.UserManager
import android.telephony.TelephonyManager
import android.testing.TestableContext
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.util.test.BroadcastInterceptingContext
import com.android.modules.utils.build.SdkLevel
import com.android.networkstack.apishim.common.UnsupportedApiLevelException
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
import com.android.server.connectivity.ClatCoordinator
import com.android.server.connectivity.ConnectivityFlags
import com.android.server.connectivity.MultinetworkPolicyTracker
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
import com.android.server.connectivity.ProxyTracker
import com.android.testutils.waitForIdle
import org.mockito.AdditionalAnswers.delegatesTo
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import java.util.concurrent.Executors
import kotlin.test.fail
internal const val HANDLER_TIMEOUT_MS = 2_000
internal const val TEST_PACKAGE_NAME = "com.android.test.package"
internal const val WIFI_WOL_IFNAME = "test_wlan_wol"
internal val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
open class FromS<Type>(val value: Type)
/**
* Base class for tests testing ConnectivityService and its satellites.
*
* This class sets up a ConnectivityService running locally in the test.
*/
// TODO (b/272685721) : make ConnectivityServiceTest smaller and faster by moving the setup
// parts into this class and moving the individual tests to multiple separate classes.
open class CSTest {
companion object {
val CSTestExecutor = Executors.newSingleThreadExecutor()
}
init {
if (!SdkLevel.isAtLeastS()) {
throw UnsupportedApiLevelException("CSTest subclasses must be annotated to only " +
"run on S+, e.g. @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)");
}
}
val instrumentationContext =
TestableContext(InstrumentationRegistry.getInstrumentation().context)
val context = CSContext(instrumentationContext)
// See constructor for default-enabled features. All queried features must be either enabled
// or disabled, because the test can't hold READ_DEVICE_CONFIG and device config utils query
// permissions using static contexts.
val enabledFeatures = HashMap<String, Boolean>().also {
it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
}
fun enableFeature(f: String) = enabledFeatures.set(f, true)
fun disableFeature(f: String) = enabledFeatures.set(f, false)
// When adding new members, consider if it's not better to build the object in CSTestHelpers
// to keep this file clean of implementation details. Generally, CSTestHelpers should only
// need changes when new details of instrumentation are needed.
val contentResolver = makeMockContentResolver(context)
val PRIMARY_USER = 0
val PRIMARY_USER_INFO = UserInfo(PRIMARY_USER, "" /* name */, UserInfo.FLAG_PRIMARY)
val PRIMARY_USER_HANDLE = UserHandle(PRIMARY_USER)
val userManager = makeMockUserManager(PRIMARY_USER_INFO, PRIMARY_USER_HANDLE)
val activityManager = makeActivityManager()
val networkStack = mock<NetworkStackClientBase>()
val csHandlerThread = HandlerThread("CSTestHandler")
val sysResources = mock<Resources>().also { initMockedResources(it) }
val packageManager = makeMockPackageManager()
val connResources = makeMockConnResources(sysResources, packageManager)
val bpfNetMaps = mock<BpfNetMaps>()
val clatCoordinator = mock<ClatCoordinator>()
val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
val alarmManager = makeMockAlarmManager()
val systemConfigManager = makeMockSystemConfigManager()
val telephonyManager = mock<TelephonyManager>().also {
doReturn(true).`when`(it).isDataCapable()
}
val deps = CSDeps()
val service = makeConnectivityService(context, deps).also { it.systemReadyInternal() }
val cm = ConnectivityManager(context, service)
val csHandler = Handler(csHandlerThread.looper)
inner class CSDeps : ConnectivityService.Dependencies() {
override fun getResources(ctx: Context) = connResources
override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps
override fun getClatCoordinator(netd: INetd?) = this@CSTest.clatCoordinator
override fun getNetworkStack() = this@CSTest.networkStack
override fun makeHandlerThread() = csHandlerThread
override fun makeProxyTracker(context: Context, connServiceHandler: Handler) = proxyTracker
override fun makeCarrierPrivilegeAuthenticator(context: Context, tm: TelephonyManager) =
if (SdkLevel.isAtLeastT()) mock<CarrierPrivilegeAuthenticator>() else null
private inner class AOOKTDeps(c: Context) : AutomaticOnOffKeepaliveTracker.Dependencies(c) {
override fun isTetheringFeatureNotChickenedOut(name: String): Boolean {
return isFeatureEnabled(context, name)
}
}
override fun makeAutomaticOnOffKeepaliveTracker(c: Context, h: Handler) =
AutomaticOnOffKeepaliveTracker(c, h, AOOKTDeps(c))
override fun makeMultinetworkPolicyTracker(c: Context, h: Handler, r: Runnable) =
MultinetworkPolicyTracker(c, h, r,
MultinetworkPolicyTrackerTestDependencies(connResources.get()))
// All queried features must be mocked, because the test cannot hold the
// READ_DEVICE_CONFIG permission and device config utils use static methods for
// checking permissions.
override fun isFeatureEnabled(context: Context?, name: String?) =
enabledFeatures[name] ?: fail("Unmocked feature $name, see CSTest.enabledFeatures")
}
inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
val pacProxyManager = mock<PacProxyManager>()
val networkPolicyManager = mock<NetworkPolicyManager>()
override fun getPackageManager() = this@CSTest.packageManager
override fun getContentResolver() = this@CSTest.contentResolver
// TODO : buff up the capabilities of this permission scheme to allow checking for
// permission rejections
override fun checkPermission(permission: String, pid: Int, uid: Int) = PERMISSION_GRANTED
override fun checkCallingOrSelfPermission(permission: String) = PERMISSION_GRANTED
// Necessary for MultinetworkPolicyTracker, which tries to register a receiver for
// all users. The test can't do that since it doesn't hold INTERACT_ACROSS_USERS.
// TODO : ensure MultinetworkPolicyTracker's BroadcastReceiver is tested ; ideally,
// just returning null should not have tests pass
override fun registerReceiverForAllUsers(
receiver: BroadcastReceiver?,
filter: IntentFilter,
broadcastPermission: String?,
scheduler: Handler?
): Intent? = null
// Create and cache user managers on the fly as necessary.
val userManagers = HashMap<UserHandle, UserManager>()
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
val asUser = mock(Context::class.java, delegatesTo<Any>(this))
doReturn(user).`when`(asUser).getUser()
doAnswer { userManagers.computeIfAbsent(user) {
mock(UserManager::class.java, delegatesTo<Any>(userManager)) }
}.`when`(asUser).getSystemService(Context.USER_SERVICE)
return asUser
}
// List of mocked services. Add additional services here or in subclasses.
override fun getSystemService(serviceName: String) = when (serviceName) {
Context.CONNECTIVITY_SERVICE -> cm
Context.PAC_PROXY_SERVICE -> pacProxyManager
Context.NETWORK_POLICY_SERVICE -> networkPolicyManager
Context.ALARM_SERVICE -> alarmManager
Context.USER_SERVICE -> userManager
Context.ACTIVITY_SERVICE -> activityManager
Context.SYSTEM_CONFIG_SERVICE -> systemConfigManager
Context.TELEPHONY_SERVICE -> telephonyManager
Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked
else -> super.getSystemService(serviceName)
}
}
// Utility methods for subclasses to use
fun waitForIdle() = csHandlerThread.waitForIdle(HANDLER_TIMEOUT_MS)
private fun emptyAgentConfig() = NetworkAgentConfig.Builder().build()
private fun defaultNc() = NetworkCapabilities.Builder()
// Add sensible defaults for agents that don't want to care
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
.addCapability(NET_CAPABILITY_NOT_ROAMING)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.build()
private fun defaultScore() = FromS<NetworkScore>(NetworkScore.Builder().build())
private fun defaultLp() = LinkProperties().apply {
addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
}
// Network agents. See CSAgentWrapper. This class contains utility methods to simplify
// creation.
fun Agent(
nac: NetworkAgentConfig = emptyAgentConfig(),
nc: NetworkCapabilities = defaultNc(),
lp: LinkProperties = defaultLp(),
score: FromS<NetworkScore> = defaultScore(),
provider: NetworkProvider? = null
) = CSAgentWrapper(context, csHandlerThread, networkStack, nac, nc, lp, score, provider)
fun Agent(vararg transports: Int, lp: LinkProperties = defaultLp()): CSAgentWrapper {
val nc = NetworkCapabilities.Builder().apply {
transports.forEach {
addTransportType(it)
}
}.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
.build()
return Agent(nc = nc, lp = lp)
}
}

View File

@@ -0,0 +1,140 @@
@file:JvmName("CsTestHelpers")
package com.android.server
import android.app.ActivityManager
import android.app.AlarmManager
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.FEATURE_BLUETOOTH
import android.content.pm.PackageManager.FEATURE_ETHERNET
import android.content.pm.PackageManager.FEATURE_WIFI
import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
import android.content.pm.UserInfo
import android.content.res.Resources
import android.net.IDnsResolver
import android.net.INetd
import android.net.metrics.IpConnectivityLog
import android.os.Handler
import android.os.HandlerThread
import android.os.SystemClock
import android.os.SystemConfigManager
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import android.test.mock.MockContentResolver
import com.android.connectivity.resources.R
import com.android.internal.util.WakeupMessage
import com.android.internal.util.test.FakeSettingsProvider
import com.android.modules.utils.build.SdkLevel
import com.android.server.ConnectivityService.Dependencies
import com.android.server.connectivity.ConnectivityResources
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.argThat
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.doNothing
import kotlin.test.fail
internal inline fun <reified T> mock() = Mockito.mock(T::class.java)
internal inline fun <reified T> any() = any(T::class.java)
internal fun makeMockContentResolver(context: Context) = MockContentResolver(context).apply {
addProvider(Settings.AUTHORITY, FakeSettingsProvider())
}
internal fun makeMockUserManager(info: UserInfo, handle: UserHandle) = mock<UserManager>().also {
doReturn(listOf(info)).`when`(it).getAliveUsers()
doReturn(listOf(handle)).`when`(it).getUserHandles(ArgumentMatchers.anyBoolean())
}
internal fun makeActivityManager() = mock<ActivityManager>().also {
if (SdkLevel.isAtLeastU()) {
doNothing().`when`(it).registerUidFrozenStateChangedCallback(any(), any())
}
}
internal fun makeMockPackageManager() = mock<PackageManager>().also { pm ->
val supported = listOf(FEATURE_WIFI, FEATURE_WIFI_DIRECT, FEATURE_BLUETOOTH, FEATURE_ETHERNET)
doReturn(true).`when`(pm).hasSystemFeature(argThat { supported.contains(it) })
}
internal fun makeMockConnResources(resources: Resources, pm: PackageManager) = mock<Context>().let {
doReturn(resources).`when`(it).resources
doReturn(pm).`when`(it).packageManager
ConnectivityResources.setResourcesContextForTest(it)
ConnectivityResources(it)
}
private val UNREASONABLY_LONG_ALARM_WAIT_MS = 1000
internal fun makeMockAlarmManager() = mock<AlarmManager>().also { am ->
val alrmHdlr = HandlerThread("TestAlarmManager").also { it.start() }.threadHandler
doAnswer {
val (_, date, _, wakeupMsg, handler) = it.arguments
wakeupMsg as WakeupMessage
handler as Handler
val delayMs = ((date as Long) - SystemClock.elapsedRealtime()).coerceAtLeast(0)
if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) {
fail("Attempting to send msg more than $UNREASONABLY_LONG_ALARM_WAIT_MS" +
"ms into the future : $delayMs")
}
alrmHdlr.postDelayed({ handler.post(wakeupMsg::onAlarm) }, wakeupMsg, delayMs)
}.`when`(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(),
any<WakeupMessage>(), any())
doAnswer {
alrmHdlr.removeCallbacksAndMessages(it.getArgument<WakeupMessage>(0))
}.`when`(am).cancel(any<WakeupMessage>())
}
internal fun makeMockSystemConfigManager() = mock<SystemConfigManager>().also {
doReturn(intArrayOf(0)).`when`(it).getSystemPermissionUids(anyString())
}
// Mocking resources used by ConnectivityService. Note these can't be defined to return the
// value returned by the mocking, because a non-null method would mean the helper would also
// return non-null and the compiler would check that, but mockito has no qualms returning null
// from a @NonNull method when stubbing. Hence, mock() = doReturn().getString() would crash
// at runtime, because getString() returns non-null String, therefore mock returns non-null String,
// and kotlinc adds an intrinsics check for that, which crashes at runtime when mockito actually
// returns null.
private fun Resources.mock(r: Int, v: Boolean) { doReturn(v).`when`(this).getBoolean(r) }
private fun Resources.mock(r: Int, v: Int) { doReturn(v).`when`(this).getInteger(r) }
private fun Resources.mock(r: Int, v: String) { doReturn(v).`when`(this).getString(r) }
private fun Resources.mock(r: Int, v: Array<String?>) { doReturn(v).`when`(this).getStringArray(r) }
private fun Resources.mock(r: Int, v: IntArray) { doReturn(v).`when`(this).getIntArray(r) }
internal fun initMockedResources(res: Resources) {
// Resources accessed through reflection need to return the id
doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(res)
.getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any())
doReturn(R.array.network_switch_type_name).`when`(res)
.getIdentifier(eq("network_switch_type_name"), eq("array"), any())
// Mock the values themselves
res.mock(R.integer.config_networkTransitionTimeout, 60_000)
res.mock(R.string.config_networkCaptivePortalServerUrl, "")
res.mock(R.array.config_wakeonlan_supported_interfaces, arrayOf(WIFI_WOL_IFNAME))
res.mock(R.array.config_networkSupportedKeepaliveCount, arrayOf("0,1", "1,3"))
res.mock(R.array.config_networkNotifySwitches, arrayOfNulls<String>(size = 0))
res.mock(R.array.config_protectedNetworks, intArrayOf(10, 11, 12, 14, 15))
res.mock(R.array.network_switch_type_name, arrayOfNulls<String>(size = 0))
res.mock(R.integer.config_networkAvoidBadWifi, 1)
res.mock(R.integer.config_activelyPreferBadWifi, 0)
res.mock(R.bool.config_cellular_radio_timesharing_capable, true)
}
private val TEST_LINGER_DELAY_MS = 400
private val TEST_NASCENT_DELAY_MS = 300
internal fun makeConnectivityService(context: Context, deps: Dependencies) = ConnectivityService(
context,
mock<IDnsResolver>(),
mock<IpConnectivityLog>(),
mock<INetd>(),
deps).also {
it.mLingerDelayMs = TEST_LINGER_DELAY_MS
it.mNascentDelayMs = TEST_NASCENT_DELAY_MS
}