From aac204b202d6a9998e19c6f2c7c533ac1da408a4 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Fri, 30 Apr 2021 20:28:29 +0900 Subject: [PATCH 1/3] Add a first CTS for NetworkScore In an effort to make reviewing easier, this implements most of the infra but only a mostly trivial test. Ignore-AOSP-First: NetworkScore incomplete in AOSP Bug: 184037351 Test: this Change-Id: Ibb9139798ce44d748e87bae79a1e23311ec8d9b6 --- .../src/android/net/cts/NetworkScoreTest.kt | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 tests/cts/net/src/android/net/cts/NetworkScoreTest.kt diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt new file mode 100644 index 0000000000..2475876077 --- /dev/null +++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.cts + +import android.Manifest.permission.MANAGE_TEST_NETWORKS +import android.content.Context +import android.net.ConnectivityManager +import android.net.LinkProperties +import android.net.NetworkAgent +import android.net.NetworkAgentConfig +import android.net.NetworkCapabilities +import android.net.NetworkProvider +import android.net.NetworkRequest +import android.net.NetworkScore +import android.os.Build +import android.os.Handler +import android.os.HandlerThread +import androidx.test.InstrumentationRegistry +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.TestableNetworkCallback +import com.android.testutils.TestableNetworkCallback.HasNetwork +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +// This test doesn't really have a constraint on how fast the methods should return. If it's +// going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio +// without affecting the run time of successful runs. Thus, set a very high timeout. +private const val TIMEOUT_MS = 30_000L +// When waiting for a NetworkCallback to determine there was no timeout, waiting is the +// only possible thing (the relevant handler is the one in the real ConnectivityService, +// and then there is the Binder call), so have a short timeout for this as it will be +// exhausted every time. +private const val NO_CALLBACK_TIMEOUT = 200L + +private val testContext: Context + get() = InstrumentationRegistry.getContext() + +private fun score(exiting: Boolean = false, primary: Boolean = false) = + NetworkScore.Builder().setExiting(exiting).setTransportPrimary(primary) + // TODO : have a constant KEEP_CONNECTED_FOR_TEST ? + .setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_FOR_HANDOVER) + .build() + +@IgnoreUpTo(Build.VERSION_CODES.R) +@RunWith(DevSdkIgnoreRunner::class) +class NetworkScoreTest { + private val mCm = testContext.getSystemService(ConnectivityManager::class.java) + private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread") + private val mHandler by lazy { Handler(mHandlerThread.looper) } + private val agentsToCleanUp = mutableListOf() + private val callbacksToCleanUp = mutableListOf() + + @Before + fun setUp() { + mHandlerThread.start() + } + + @After + fun tearDown() { + agentsToCleanUp.forEach { it.unregister() } + mHandlerThread.quitSafely() + callbacksToCleanUp.forEach { mCm.unregisterNetworkCallback(it) } + } + + // Returns a networkCallback that sends onAvailable on the best network with TRANSPORT_TEST. + private fun makeTestNetworkCallback() = TestableNetworkCallback(TIMEOUT_MS).also { cb -> + mCm.registerBestMatchingNetworkCallback(NetworkRequest.Builder().clearCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_TEST).build(), cb, mHandler) + callbacksToCleanUp.add(cb) + } + + // TestNetworkCallback is made to interact with a wrapper of NetworkAgent, because it's + // made for ConnectivityServiceTest. + // TODO : have TestNetworkCallback work for NetworkAgent too and remove this class. + private class AgentWrapper(val agent: NetworkAgent) : HasNetwork { + override val network = agent.network + fun sendNetworkScore(s: NetworkScore) = agent.sendNetworkScore(s) + } + + private fun createTestNetworkAgent( + // The network always has TRANSPORT_TEST, plus optional transports + optionalTransports: IntArray = IntArray(size = 0), + everUserSelected: Boolean = false, + acceptUnvalidated: Boolean = false, + isExiting: Boolean = false, + isPrimary: Boolean = false + ): AgentWrapper { + val nc = NetworkCapabilities.Builder().apply { + addTransportType(NetworkCapabilities.TRANSPORT_TEST) + optionalTransports.forEach { addTransportType(it) } + // Add capabilities that are common, just for realism. It's not strictly necessary + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) + // Remove capabilities that a test network agent shouldn't have and that are not + // needed for the purposes of this test. + removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + if (optionalTransports.contains(NetworkCapabilities.TRANSPORT_VPN)) { + addTransportType(NetworkCapabilities.TRANSPORT_VPN) + removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + } + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) + }.build() + val config = NetworkAgentConfig.Builder() + .setExplicitlySelected(everUserSelected) + .setUnvalidatedConnectivityAcceptable(acceptUnvalidated) + .build() + val score = score(exiting = isExiting, primary = isPrimary) + val context = testContext + val looper = mHandlerThread.looper + val agent = object : NetworkAgent(context, looper, "NetworkScore test agent", nc, + LinkProperties(), score, config, NetworkProvider(context, looper, + "NetworkScore test provider")) {}.also { + agentsToCleanUp.add(it) + } + runWithShellPermissionIdentity({ agent.register() }, MANAGE_TEST_NETWORKS) + agent.markConnected() + return AgentWrapper(agent) + } + + @Test + fun testExitingLosesAndOldSatisfierWins() { + val cb = makeTestNetworkCallback() + val agent1 = createTestNetworkAgent() + cb.expectAvailableThenValidatedCallbacks(agent1) + val agent2 = createTestNetworkAgent() + // Because the existing network must win, the callback stays on agent1. + cb.assertNoCallback(NO_CALLBACK_TIMEOUT) + agent1.sendNetworkScore(score(exiting = true)) + // Now that agent1 is exiting, the callback is satisfied by agent2. + cb.expectAvailableCallbacks(agent2.network) + agent1.sendNetworkScore(score(exiting = false)) + // Agent1 is no longer exiting, but agent2 is the current satisfier. + cb.assertNoCallback(NO_CALLBACK_TIMEOUT) + } +} From 2d2ecc3cfbe37bd0a97cc139b4865910e2c96b03 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Thu, 6 May 2021 20:22:46 +0900 Subject: [PATCH 2/3] Add some CTS tests for NetworkScore. Ignore-AOSP-First: NetworkScore incomplete in AOSP Bug: 184037351 Test: this Change-Id: I2edba51351cd4c71727663aa85b1d5141bff2a15 --- .../src/android/net/cts/NetworkScoreTest.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt index 2475876077..4566c9a55b 100644 --- a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt @@ -25,6 +25,8 @@ import android.net.NetworkCapabilities import android.net.NetworkProvider import android.net.NetworkRequest import android.net.NetworkScore +import android.net.VpnManager +import android.net.VpnTransportInfo import android.os.Build import android.os.Handler import android.os.HandlerThread @@ -116,6 +118,7 @@ class NetworkScoreTest { if (optionalTransports.contains(NetworkCapabilities.TRANSPORT_VPN)) { addTransportType(NetworkCapabilities.TRANSPORT_VPN) removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + setTransportInfo(VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE, null)) } addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) }.build() @@ -151,4 +154,45 @@ class NetworkScoreTest { // Agent1 is no longer exiting, but agent2 is the current satisfier. cb.assertNoCallback(NO_CALLBACK_TIMEOUT) } + + @Test + fun testVpnWins() { + val cb = makeTestNetworkCallback() + val agent1 = createTestNetworkAgent() + cb.expectAvailableThenValidatedCallbacks(agent1.network) + val agent2 = createTestNetworkAgent(intArrayOf(NetworkCapabilities.TRANSPORT_VPN)) + // VPN wins out against agent1 even before it's validated (hence the "then validated", + // because it becomes the best network for this callback before it validates) + cb.expectAvailableThenValidatedCallbacks(agent2.network) + } + + @Test + fun testEverUserSelectedAcceptUnvalidatedWins() { + val cb = makeTestNetworkCallback() + val agent1 = createTestNetworkAgent() + cb.expectAvailableThenValidatedCallbacks(agent1.network) + val agent2 = createTestNetworkAgent(everUserSelected = true, acceptUnvalidated = true) + // agent2 wins out against agent1 even before it's validated, because user-selected and + // accept unvalidated networks should win against even networks that are validated. + cb.expectAvailableThenValidatedCallbacks(agent2.network) + } + + @Test + fun testPreferredTransportOrder() { + val cb = makeTestNetworkCallback() + val agentCell = createTestNetworkAgent(intArrayOf(NetworkCapabilities.TRANSPORT_CELLULAR)) + cb.expectAvailableThenValidatedCallbacks(agentCell.network) + val agentWifi = createTestNetworkAgent(intArrayOf(NetworkCapabilities.TRANSPORT_WIFI)) + // In the absence of other discriminating factors, agentWifi wins against agentCell because + // of its better transport, but only after it validates. + cb.expectAvailableDoubleValidatedCallbacks(agentWifi) + val agentEth = createTestNetworkAgent(intArrayOf(NetworkCapabilities.TRANSPORT_ETHERNET)) + // Likewise, agentEth wins against agentWifi after validation because of its better + // transport. + cb.expectAvailableCallbacksValidated(agentEth) + } + + // TODO (b/187929636) : add a test making sure that validated networks win over unvalidated + // ones. Right now this is not possible because this CTS can't directly manipulate the + // validation state of a network. } From ee93b3d3b34ba59c1d7ba2b5df4c299886a8d747 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Thu, 6 May 2021 23:27:53 +0900 Subject: [PATCH 3/3] Add CTS for NetworkScore.setTransportPrimary Ignore-AOSP-First: NetworkScore incomplete in AOSP Bug: 184037351 Test: this Change-Id: I81bf8969ace3cb6826b30c5bbd0b896c64c73c57 --- .../src/android/net/cts/NetworkScoreTest.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt index 4566c9a55b..8f17199de4 100644 --- a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt @@ -192,6 +192,25 @@ class NetworkScoreTest { cb.expectAvailableCallbacksValidated(agentEth) } + @Test + fun testTransportPrimary() { + val cb = makeTestNetworkCallback() + val agent1 = createTestNetworkAgent() + cb.expectAvailableThenValidatedCallbacks(agent1) + val agent2 = createTestNetworkAgent() + // Because the existing network must win, the callback stays on agent1. + cb.assertNoCallback(NO_CALLBACK_TIMEOUT) + agent2.sendNetworkScore(score(primary = true)) + // Now that agent2 is primary, the callback is satisfied by agent2. + cb.expectAvailableCallbacks(agent2.network) + agent1.sendNetworkScore(score(primary = true)) + // Agent1 is primary too, but agent2 is the current satisfier + cb.assertNoCallback(NO_CALLBACK_TIMEOUT) + agent2.sendNetworkScore(score(primary = false)) + // Now agent1 is primary and agent2 isn't + cb.expectAvailableCallbacks(agent1.network) + } + // TODO (b/187929636) : add a test making sure that validated networks win over unvalidated // ones. Right now this is not possible because this CTS can't directly manipulate the // validation state of a network.