Merge changes I70384830,Ie97c2611,I7064edda,I290136b4,Ide697f65, ...
* changes: ethernet: add test that covers link up / down behavior ethernet: add updateConfiguration support to EthernetManagerTest ethernet: add support for enable/disableInterface in tests ethernet: clean up HandlerExecutor usage in test ethernet: address test review comments ethernet: get MTU size directly from TestNetworkInterface
This commit is contained in:
commit
bbae90476d
1 changed files with 175 additions and 69 deletions
|
|
@ -30,11 +30,14 @@ import android.net.EthernetManager.STATE_LINK_DOWN
|
|||
import android.net.EthernetManager.STATE_LINK_UP
|
||||
import android.net.EthernetManager.TetheredInterfaceCallback
|
||||
import android.net.EthernetManager.TetheredInterfaceRequest
|
||||
import android.net.EthernetNetworkManagementException
|
||||
import android.net.EthernetNetworkSpecifier
|
||||
import android.net.EthernetNetworkUpdateRequest
|
||||
import android.net.InetAddresses
|
||||
import android.net.IpConfiguration
|
||||
import android.net.MacAddress
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
|
||||
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
|
||||
import android.net.NetworkCapabilities.TRANSPORT_TEST
|
||||
|
|
@ -44,8 +47,8 @@ import android.net.TestNetworkManager
|
|||
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.HandlerExecutor
|
||||
import android.os.Looper
|
||||
import android.os.OutcomeReceiver
|
||||
import android.os.SystemProperties
|
||||
import android.platform.test.annotations.AppModeFull
|
||||
import android.util.ArraySet
|
||||
|
|
@ -100,29 +103,34 @@ class EthernetManagerTest {
|
|||
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
|
||||
private val em by lazy { context.getSystemService(EthernetManager::class.java) }
|
||||
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
|
||||
private val ifaceListener = EthernetStateListener()
|
||||
private val createdIfaces = ArrayList<EthernetTestInterface>()
|
||||
private val addedListeners = ArrayList<EthernetStateListener>()
|
||||
private val networkRequests = ArrayList<TestableNetworkCallback>()
|
||||
private val registeredCallbacks = ArrayList<TestableNetworkCallback>()
|
||||
|
||||
private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
|
||||
|
||||
private class EthernetTestInterface(
|
||||
context: Context,
|
||||
private val handler: Handler
|
||||
private val handler: Handler,
|
||||
hasCarrier: Boolean
|
||||
) {
|
||||
private val tapInterface: TestNetworkInterface
|
||||
private val packetReader: TapPacketReader
|
||||
private val raResponder: RouterAdvertisementResponder
|
||||
val interfaceName get() = tapInterface.interfaceName
|
||||
private val tnm: TestNetworkManager
|
||||
val name get() = tapInterface.interfaceName
|
||||
|
||||
init {
|
||||
tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
|
||||
val tnm = context.getSystemService(TestNetworkManager::class.java)
|
||||
tnm.createTapInterface(false /* bringUp */)
|
||||
tnm = runAsShell(MANAGE_TEST_NETWORKS) {
|
||||
context.getSystemService(TestNetworkManager::class.java)
|
||||
}
|
||||
val mtu = 1500
|
||||
tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
|
||||
tnm.createTapInterface(hasCarrier, false /* bringUp */)
|
||||
}
|
||||
val mtu = tapInterface.mtu
|
||||
packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
|
||||
raResponder = RouterAdvertisementResponder(packetReader)
|
||||
raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
|
||||
|
|
@ -132,6 +140,12 @@ class EthernetManagerTest {
|
|||
raResponder.start()
|
||||
}
|
||||
|
||||
fun setCarrierEnabled(enabled: Boolean) {
|
||||
runAsShell(MANAGE_TEST_NETWORKS) {
|
||||
tnm.setCarrierEnabled(tapInterface, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
raResponder.stop()
|
||||
handler.post({ packetReader.stop() })
|
||||
|
|
@ -172,7 +186,7 @@ class EthernetManagerTest {
|
|||
}
|
||||
|
||||
fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
|
||||
expectCallback(createChangeEvent(iface.interfaceName, state, role))
|
||||
expectCallback(createChangeEvent(iface.name, state, role))
|
||||
}
|
||||
|
||||
fun createChangeEvent(iface: String, state: Int, role: Int) =
|
||||
|
|
@ -190,7 +204,7 @@ class EthernetManagerTest {
|
|||
}
|
||||
|
||||
fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
|
||||
eventuallyExpect(iface.interfaceName, state, role)
|
||||
eventuallyExpect(iface.name, state, role)
|
||||
}
|
||||
|
||||
fun assertNoCallback() {
|
||||
|
|
@ -227,6 +241,35 @@ class EthernetManagerTest {
|
|||
}
|
||||
}
|
||||
|
||||
private class EthernetOutcomeReceiver :
|
||||
OutcomeReceiver<String, EthernetNetworkManagementException> {
|
||||
private val result = CompletableFuture<String>()
|
||||
|
||||
override fun onResult(iface: String) {
|
||||
result.complete(iface)
|
||||
}
|
||||
|
||||
override fun onError(e: EthernetNetworkManagementException) {
|
||||
result.completeExceptionally(e)
|
||||
}
|
||||
|
||||
fun expectResult(expected: String) {
|
||||
assertEquals(expected, result.get(TIMEOUT_MS, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
|
||||
fun expectError() {
|
||||
// Assert that the future fails with EthernetNetworkManagementException from the
|
||||
// completeExceptionally() call inside onUnavailable.
|
||||
assertFailsWith(EthernetNetworkManagementException::class) {
|
||||
try {
|
||||
result.get()
|
||||
} catch (e: ExecutionException) {
|
||||
throw e.cause!!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
setIncludeTestInterfaces(true)
|
||||
|
|
@ -243,26 +286,28 @@ class EthernetManagerTest {
|
|||
for (listener in addedListeners) {
|
||||
em.removeInterfaceStateListener(listener)
|
||||
}
|
||||
networkRequests.forEach { cm.unregisterNetworkCallback(it) }
|
||||
registeredCallbacks.forEach { cm.unregisterNetworkCallback(it) }
|
||||
releaseTetheredInterface()
|
||||
}
|
||||
|
||||
private fun addInterfaceStateListener(listener: EthernetStateListener) {
|
||||
runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
|
||||
em.addInterfaceStateListener(HandlerExecutor(Handler(Looper.getMainLooper())), listener)
|
||||
em.addInterfaceStateListener(handler::post, listener)
|
||||
}
|
||||
addedListeners.add(listener)
|
||||
}
|
||||
|
||||
private fun createInterface(): EthernetTestInterface {
|
||||
private fun createInterface(hasCarrier: Boolean = true): EthernetTestInterface {
|
||||
val iface = EthernetTestInterface(
|
||||
context,
|
||||
Handler(Looper.getMainLooper())
|
||||
handler,
|
||||
hasCarrier
|
||||
).also { createdIfaces.add(it) }
|
||||
with(ifaceListener) {
|
||||
// when an interface comes up, we should always see a down cb before an up cb.
|
||||
eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
|
||||
expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
|
||||
|
||||
// when an interface comes up, we should always see a down cb before an up cb.
|
||||
ifaceListener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
|
||||
if (hasCarrier) {
|
||||
ifaceListener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
|
||||
}
|
||||
return iface
|
||||
}
|
||||
|
|
@ -282,18 +327,20 @@ class EthernetManagerTest {
|
|||
private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback {
|
||||
return TestableNetworkCallback().also {
|
||||
cm.requestNetwork(request, it)
|
||||
networkRequests.add(it)
|
||||
registeredCallbacks.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseNetwork(cb: TestableNetworkCallback) {
|
||||
cm.unregisterNetworkCallback(cb)
|
||||
networkRequests.remove(cb)
|
||||
private fun registerNetworkListener(request: NetworkRequest): TestableNetworkCallback {
|
||||
return TestableNetworkCallback().also {
|
||||
cm.registerNetworkCallback(request, it)
|
||||
registeredCallbacks.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestTetheredInterface() = TetheredInterfaceListener().also {
|
||||
tetheredInterfaceRequest = runAsShell(NETWORK_SETTINGS) {
|
||||
em.requestTetheredInterface(HandlerExecutor(Handler(Looper.getMainLooper())), it)
|
||||
em.requestTetheredInterface(handler::post, it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -304,22 +351,65 @@ class EthernetManagerTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun releaseRequest(cb: TestableNetworkCallback) {
|
||||
cm.unregisterNetworkCallback(cb)
|
||||
registeredCallbacks.remove(cb)
|
||||
}
|
||||
|
||||
private fun disableInterface(iface: EthernetTestInterface) = EthernetOutcomeReceiver().also {
|
||||
runAsShell(MANAGE_TEST_NETWORKS) {
|
||||
em.disableInterface(iface.name, handler::post, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableInterface(iface: EthernetTestInterface) = EthernetOutcomeReceiver().also {
|
||||
runAsShell(MANAGE_TEST_NETWORKS) {
|
||||
em.enableInterface(iface.name, handler::post, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateConfiguration(
|
||||
iface: EthernetTestInterface,
|
||||
ipConfig: IpConfiguration? = null,
|
||||
capabilities: NetworkCapabilities? = null
|
||||
) = EthernetOutcomeReceiver().also {
|
||||
runAsShell(MANAGE_TEST_NETWORKS) {
|
||||
em.updateConfiguration(
|
||||
iface.name,
|
||||
EthernetNetworkUpdateRequest.Builder()
|
||||
.setIpConfiguration(ipConfig)
|
||||
.setNetworkCapabilities(capabilities).build(),
|
||||
handler::post,
|
||||
it)
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkRequest.Builder does not create a copy of the passed NetworkRequest, so in order to
|
||||
// keep ETH_REQUEST as it is, a defensive copy is created here.
|
||||
private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
|
||||
NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
|
||||
.setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
|
||||
|
||||
// It can take multiple seconds for the network to become available.
|
||||
private fun TestableNetworkCallback.expectAvailable() =
|
||||
expectCallback<Available>(anyNetwork(), 5000/*ms timeout*/).network
|
||||
expectCallback<Available>(anyNetwork(), 5000 /* ms timeout */).network
|
||||
|
||||
// b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use
|
||||
// eventuallyExpect(Lost::class) instead.
|
||||
private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) =
|
||||
eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true }
|
||||
|
||||
private fun TestableNetworkCallback.assertNotLost(n: Network? = null) =
|
||||
private fun TestableNetworkCallback.assertNeverLost(n: Network? = null) =
|
||||
assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) }
|
||||
|
||||
private fun TestableNetworkCallback.assertNeverAvailable(n: Network? = null) =
|
||||
assertNoCallbackThat() { it is Available && (n?.equals(it.network) ?: true) }
|
||||
|
||||
private fun TestableNetworkCallback.expectCapabilitiesWithInterfaceName(name: String) =
|
||||
expectCapabilitiesThat(anyNetwork()) {
|
||||
it.networkSpecifier == EthernetNetworkSpecifier(name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCallbacks() {
|
||||
// If an interface exists when the callback is registered, it is reported on registration.
|
||||
|
|
@ -398,11 +488,8 @@ class EthernetManagerTest {
|
|||
assertTrue(polledIfaces.add(iface), "Duplicate interface $iface returned")
|
||||
assertTrue(ifaces.contains(iface), "Untracked interface $iface returned")
|
||||
// If the event's iface was created in the test, additional criteria can be validated.
|
||||
createdIfaces.find { it.interfaceName.equals(iface) }?.let {
|
||||
assertEquals(event,
|
||||
listener.createChangeEvent(it.interfaceName,
|
||||
STATE_LINK_UP,
|
||||
ROLE_CLIENT))
|
||||
createdIfaces.find { it.name.equals(iface) }?.let {
|
||||
assertEquals(event, listener.createChangeEvent(it.name, STATE_LINK_UP, ROLE_CLIENT))
|
||||
}
|
||||
}
|
||||
// Assert all callbacks are accounted for.
|
||||
|
|
@ -411,87 +498,75 @@ class EthernetManagerTest {
|
|||
|
||||
@Test
|
||||
fun testGetInterfaceList() {
|
||||
setIncludeTestInterfaces(true)
|
||||
|
||||
// Create two test interfaces and check the return list contains the interface names.
|
||||
val iface1 = createInterface()
|
||||
val iface2 = createInterface()
|
||||
var ifaces = em.getInterfaceList()
|
||||
assertTrue(ifaces.size > 0)
|
||||
assertTrue(ifaces.contains(iface1.interfaceName))
|
||||
assertTrue(ifaces.contains(iface2.interfaceName))
|
||||
assertTrue(ifaces.contains(iface1.name))
|
||||
assertTrue(ifaces.contains(iface2.name))
|
||||
|
||||
// Remove one existing test interface and check the return list doesn't contain the
|
||||
// removed interface name.
|
||||
removeInterface(iface1)
|
||||
ifaces = em.getInterfaceList()
|
||||
assertFalse(ifaces.contains(iface1.interfaceName))
|
||||
assertTrue(ifaces.contains(iface2.interfaceName))
|
||||
assertFalse(ifaces.contains(iface1.name))
|
||||
assertTrue(ifaces.contains(iface2.name))
|
||||
|
||||
removeInterface(iface2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNetworkRequest_withSingleExistingInterface() {
|
||||
setIncludeTestInterfaces(true)
|
||||
createInterface()
|
||||
|
||||
// install a listener which will later be used to verify the Lost callback
|
||||
val listenerCb = TestableNetworkCallback()
|
||||
cm.registerNetworkCallback(ETH_REQUEST, listenerCb)
|
||||
networkRequests.add(listenerCb)
|
||||
val listenerCb = registerNetworkListener(ETH_REQUEST)
|
||||
|
||||
val cb = requestNetwork(ETH_REQUEST)
|
||||
val network = cb.expectAvailable()
|
||||
|
||||
cb.assertNotLost()
|
||||
releaseNetwork(cb)
|
||||
cb.assertNeverLost()
|
||||
releaseRequest(cb)
|
||||
listenerCb.eventuallyExpectLost(network)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNetworkRequest_beforeSingleInterfaceIsUp() {
|
||||
setIncludeTestInterfaces(true)
|
||||
|
||||
val cb = requestNetwork(ETH_REQUEST)
|
||||
|
||||
// bring up interface after network has been requested
|
||||
// bring up interface after network has been requested.
|
||||
// Note: there is no guarantee that the NetworkRequest has been processed before the
|
||||
// interface is actually created. That being said, it takes a few seconds between calling
|
||||
// createInterface and the interface actually being properly registered with the ethernet
|
||||
// module, so it is extremely unlikely that the CS handler thread has not run until then.
|
||||
val iface = createInterface()
|
||||
val network = cb.expectAvailable()
|
||||
|
||||
// remove interface before network request has been removed
|
||||
cb.assertNotLost()
|
||||
cb.assertNeverLost()
|
||||
removeInterface(iface)
|
||||
cb.eventuallyExpectLost()
|
||||
|
||||
releaseNetwork(cb)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNetworkRequest_withMultipleInterfaces() {
|
||||
setIncludeTestInterfaces(true)
|
||||
|
||||
val iface1 = createInterface()
|
||||
val iface2 = createInterface()
|
||||
|
||||
val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
|
||||
val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
|
||||
|
||||
val network = cb.expectAvailable()
|
||||
cb.expectCapabilitiesThat(network) {
|
||||
it.networkSpecifier == EthernetNetworkSpecifier(iface2.interfaceName)
|
||||
}
|
||||
cb.expectCapabilitiesWithInterfaceName(iface2.name)
|
||||
|
||||
removeInterface(iface1)
|
||||
cb.assertNotLost()
|
||||
cb.assertNeverLost()
|
||||
removeInterface(iface2)
|
||||
cb.eventuallyExpectLost()
|
||||
|
||||
releaseNetwork(cb)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNetworkRequest_withInterfaceBeingReplaced() {
|
||||
setIncludeTestInterfaces(true)
|
||||
val iface1 = createInterface()
|
||||
|
||||
val cb = requestNetwork(ETH_REQUEST)
|
||||
|
|
@ -499,36 +574,67 @@ class EthernetManagerTest {
|
|||
|
||||
// create another network and verify the request sticks to the current network
|
||||
val iface2 = createInterface()
|
||||
cb.assertNotLost()
|
||||
cb.assertNeverLost()
|
||||
|
||||
// remove iface1 and verify the request brings up iface2
|
||||
removeInterface(iface1)
|
||||
cb.eventuallyExpectLost(network)
|
||||
val network2 = cb.expectAvailable()
|
||||
|
||||
releaseNetwork(cb)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNetworkRequest_withMultipleInterfacesAndRequests() {
|
||||
setIncludeTestInterfaces(true)
|
||||
val iface1 = createInterface()
|
||||
val iface2 = createInterface()
|
||||
|
||||
val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.interfaceName))
|
||||
val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
|
||||
val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.name))
|
||||
val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
|
||||
val cb3 = requestNetwork(ETH_REQUEST)
|
||||
|
||||
cb1.expectAvailable()
|
||||
cb1.expectCapabilitiesWithInterfaceName(iface1.name)
|
||||
cb2.expectAvailable()
|
||||
cb2.expectCapabilitiesWithInterfaceName(iface2.name)
|
||||
// this request can be matched by either network.
|
||||
cb3.expectAvailable()
|
||||
|
||||
cb1.assertNotLost()
|
||||
cb2.assertNotLost()
|
||||
cb3.assertNotLost()
|
||||
cb1.assertNeverLost()
|
||||
cb2.assertNeverLost()
|
||||
cb3.assertNeverLost()
|
||||
}
|
||||
|
||||
releaseNetwork(cb1)
|
||||
releaseNetwork(cb2)
|
||||
releaseNetwork(cb3)
|
||||
@Test
|
||||
fun testNetworkRequest_ensureProperRefcounting() {
|
||||
// create first request before interface is up / exists; create another request after it has
|
||||
// been created; release one of them and check that the network stays up.
|
||||
val listener = registerNetworkListener(ETH_REQUEST)
|
||||
val cb1 = requestNetwork(ETH_REQUEST)
|
||||
|
||||
val iface = createInterface()
|
||||
val network = cb1.expectAvailable()
|
||||
|
||||
val cb2 = requestNetwork(ETH_REQUEST)
|
||||
cb2.expectAvailable()
|
||||
|
||||
// release the first request; this used to trigger b/197548738
|
||||
releaseRequest(cb1)
|
||||
|
||||
cb2.assertNeverLost()
|
||||
releaseRequest(cb2)
|
||||
listener.eventuallyExpectLost(network)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNetworkRequest_forInterfaceWhileTogglingCarrier() {
|
||||
val iface = createInterface(false /* hasCarrier */)
|
||||
|
||||
val cb = requestNetwork(ETH_REQUEST)
|
||||
cb.assertNeverAvailable()
|
||||
|
||||
iface.setCarrierEnabled(true)
|
||||
cb.expectAvailable()
|
||||
|
||||
iface.setCarrierEnabled(false)
|
||||
cb.eventuallyExpectLost()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue