Loading tests/lib/src/com/android/testutils/TestableNetworkCallback.kt 0 → 100644 +343 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2019 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 com.android.testutils import android.net.ConnectivityManager.NetworkCallback import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import com.android.testutils.RecorderCallback.CallbackRecord.Available import com.android.testutils.RecorderCallback.CallbackRecord.BlockedStatus import com.android.testutils.RecorderCallback.CallbackRecord.CapabilitiesChanged import com.android.testutils.RecorderCallback.CallbackRecord.LinkPropertiesChanged import com.android.testutils.RecorderCallback.CallbackRecord.Lost import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail object NULL_NETWORK : Network(-1) private val Int.capabilityName get() = NetworkCapabilities.capabilityNameOf(this) open class RecorderCallback : NetworkCallback() { sealed class CallbackRecord { // To get equals(), hashcode(), componentN() etc for free, the child classes of // this class are data classes. But while data classes can inherit from other classes, // they may only have visible members in the constructors, so they couldn't declare // a constructor with a non-val arg to pass to CallbackRecord. Instead, force all // subclasses to implement a `network' property, which can be done in a data class // constructor by specifying override. abstract val network: Network data class Available(override val network: Network) : CallbackRecord() data class CapabilitiesChanged( override val network: Network, val caps: NetworkCapabilities ) : CallbackRecord() data class LinkPropertiesChanged( override val network: Network, val lp: LinkProperties ) : CallbackRecord() data class Suspended(override val network: Network) : CallbackRecord() data class Resumed(override val network: Network) : CallbackRecord() data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackRecord() data class Lost(override val network: Network) : CallbackRecord() data class Unavailable private constructor( override val network: Network ) : CallbackRecord() { constructor() : this(NULL_NETWORK) } data class BlockedStatus( override val network: Network, val blocked: Boolean ) : CallbackRecord() // Convenience constants for expecting a type companion object { @JvmField val AVAILABLE = Available::class @JvmField val NETWORK_CAPS_UPDATED = CapabilitiesChanged::class @JvmField val LINK_PROPERTIES_CHANGED = LinkPropertiesChanged::class @JvmField val SUSPENDED = Suspended::class @JvmField val RESUMED = Resumed::class @JvmField val LOSING = Losing::class @JvmField val LOST = Lost::class @JvmField val UNAVAILABLE = Unavailable::class @JvmField val BLOCKED_STATUS = BlockedStatus::class } } protected val history = ArrayTrackRecord<CallbackRecord>().newReadHead() override fun onAvailable(network: Network) { history.add(Available(network)) } override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) { history.add(CapabilitiesChanged(network, caps)) } override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) { history.add(LinkPropertiesChanged(network, lp)) } override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { history.add(BlockedStatus(network, blocked)) } override fun onNetworkSuspended(network: Network) { history.add(CallbackRecord.Suspended(network)) } override fun onNetworkResumed(network: Network) { history.add(CallbackRecord.Resumed(network)) } override fun onLosing(network: Network, maxMsToLive: Int) { history.add(CallbackRecord.Losing(network, maxMsToLive)) } override fun onLost(network: Network) { history.add(CallbackRecord.Lost(network)) } override fun onUnavailable() { history.add(CallbackRecord.Unavailable()) } } typealias CallbackType = KClass<out RecorderCallback.CallbackRecord> const val DEFAULT_TIMEOUT = 200L // ms open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) : RecorderCallback() { // The last available network. Null if the last available network was lost since. val lastAvailableNetwork: Network? get() = when (val it = history.lastOrNull { it is Available || it is Lost }) { is Available -> it.network else -> null } fun pollForNextCallback(timeoutMs: Long = defaultTimeoutMs): CallbackRecord { return history.poll(timeoutMs) ?: fail("Did not receive callback after ${timeoutMs}ms") } // Make open for use in ConnectivityServiceTest which is the only one knowing its handlers. @JvmOverloads open fun assertNoCallback(timeoutMs: Long = defaultTimeoutMs) { val cb = history.poll(timeoutMs) if (null != cb) fail("Expected no callback but got $cb") } inline fun <reified T : CallbackRecord> expectCallback( network: Network, timeoutMs: Long = defaultTimeoutMs ): T = pollForNextCallback(timeoutMs).let { if (it !is T || it.network != network) { fail("Unexpected callback : $it, expected ${T::class} with Network[$network]") } else { it } } fun expectCallbackThat( timeoutMs: Long = defaultTimeoutMs, valid: (CallbackRecord) -> Boolean ) = pollForNextCallback(timeoutMs).also { assertTrue(valid(it), "Unexpected callback : $it") } fun expectCapabilitiesThat( net: Network, tmt: Long = defaultTimeoutMs, valid: (NetworkCapabilities) -> Boolean ): CapabilitiesChanged { return expectCallback<CapabilitiesChanged>(net, tmt).also { assertTrue(valid(it.caps), "Capabilities don't match expectations ${it.caps}") } } fun expectLinkPropertiesThat( net: Network, tmt: Long = defaultTimeoutMs, valid: (LinkProperties) -> Boolean ): LinkPropertiesChanged { return expectCallback<LinkPropertiesChanged>(net, tmt).also { assertTrue(valid(it.lp), "LinkProperties don't match expectations ${it.lp}") } } // Expects onAvailable and the callbacks that follow it. These are: // - onSuspended, iff the network was suspended when the callbacks fire. // - onCapabilitiesChanged. // - onLinkPropertiesChanged. // - onBlockedStatusChanged. // // @param network the network to expect the callbacks on. // @param suspended whether to expect a SUSPENDED callback. // @param validated the expected value of the VALIDATED capability in the // onCapabilitiesChanged callback. // @param tmt how long to wait for the callbacks. fun expectAvailableCallbacks( net: Network, suspended: Boolean = false, validated: Boolean = true, blocked: Boolean = false, tmt: Long = defaultTimeoutMs ) { expectCallback<Available>(net, tmt) if (suspended) { expectCallback<CallbackRecord.Suspended>(net, tmt) } expectCapabilitiesThat(net, tmt) { validated == it.hasCapability(NET_CAPABILITY_VALIDATED) } expectCallback<LinkPropertiesChanged>(net, tmt) expectBlockedStatusCallback(blocked, net) } // Backward compatibility for existing Java code. Use named arguments instead and remove all // these when there is no user left. fun expectAvailableAndSuspendedCallbacks( net: Network, validated: Boolean, tmt: Long = defaultTimeoutMs ) = expectAvailableCallbacks(net, suspended = true, validated = validated, tmt = tmt) fun expectBlockedStatusCallback(blocked: Boolean, net: Network, tmt: Long = defaultTimeoutMs) { expectCallback<BlockedStatus>(net, tmt).also { assertEquals(it.blocked, blocked, "Unexpected blocked status ${it.blocked}") } } // Expects the available callbacks (where the onCapabilitiesChanged must contain the // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the // one we just sent. // TODO: this is likely a bug. Fix it and remove this method. fun expectAvailableDoubleValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) { val mark = history.mark expectAvailableCallbacks(net, tmt = tmt) val firstCaps = history.poll(tmt, mark) { it is CapabilitiesChanged } assertEquals(firstCaps, expectCallback<CapabilitiesChanged>(net, tmt)) } // Expects the available callbacks where the onCapabilitiesChanged must not have validated, // then expects another onCapabilitiesChanged that has the validated bit set. This is used // when a network connects and satisfies a callback, and then immediately validates. fun expectAvailableThenValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) { expectAvailableCallbacks(net, validated = false, tmt = tmt) expectCapabilitiesThat(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) } } // Temporary Java compat measure : have MockNetworkAgent implement this so that all existing // calls with networkAgent can be routed through here without moving MockNetworkAgent. // TODO: clean this up, remove this method. interface HasNetwork { val network: Network } @JvmOverloads open fun <T : CallbackRecord> expectCallback( type: KClass<T>, n: HasNetwork?, timeoutMs: Long = defaultTimeoutMs ) = pollForNextCallback(timeoutMs).also { val network = n?.network ?: NULL_NETWORK // TODO : remove this .java access if the tests ever use kotlin-reflect. At the time of // this writing this would be the only use of this library in the tests. assertTrue(type.java.isInstance(it) && it.network == network, "Unexpected callback : $it, expected ${type.java} with Network[$network]") } as T fun expectAvailableCallbacks( n: HasNetwork, suspended: Boolean, validated: Boolean, blocked: Boolean, timeoutMs: Long ) = expectAvailableCallbacks(n.network, suspended, validated, blocked, timeoutMs) fun expectAvailableAndSuspendedCallbacks(n: HasNetwork, expectValidated: Boolean) { expectAvailableAndSuspendedCallbacks(n.network, expectValidated) } fun expectAvailableCallbacksValidated(n: HasNetwork) { expectAvailableCallbacks(n.network) } fun expectAvailableCallbacksValidatedAndBlocked(n: HasNetwork) { expectAvailableCallbacks(n.network, blocked = true) } fun expectAvailableCallbacksUnvalidated(n: HasNetwork) { expectAvailableCallbacks(n.network, validated = false) } fun expectAvailableCallbacksUnvalidatedAndBlocked(n: HasNetwork) { expectAvailableCallbacks(n.network, validated = false, blocked = true) } fun expectAvailableDoubleValidatedCallbacks(n: HasNetwork) { expectAvailableDoubleValidatedCallbacks(n.network, defaultTimeoutMs) } fun expectAvailableThenValidatedCallbacks(n: HasNetwork) { expectAvailableThenValidatedCallbacks(n.network, defaultTimeoutMs) } @JvmOverloads fun expectLinkPropertiesThat( n: HasNetwork, tmt: Long = defaultTimeoutMs, valid: (LinkProperties) -> Boolean ) = expectLinkPropertiesThat(n.network, tmt, valid) @JvmOverloads fun expectCapabilitiesThat( n: HasNetwork, tmt: Long = defaultTimeoutMs, valid: (NetworkCapabilities) -> Boolean ) = expectCapabilitiesThat(n.network, tmt, valid) @JvmOverloads fun expectCapabilitiesWith( capability: Int, n: HasNetwork, timeoutMs: Long = defaultTimeoutMs ): NetworkCapabilities { return expectCapabilitiesThat(n.network, timeoutMs) { it.hasCapability(capability) }.caps } @JvmOverloads fun expectCapabilitiesWithout( capability: Int, n: HasNetwork, timeoutMs: Long = defaultTimeoutMs ): NetworkCapabilities { return expectCapabilitiesThat(n.network, timeoutMs) { !it.hasCapability(capability) }.caps } fun expectBlockedStatusCallback(expectBlocked: Boolean, n: HasNetwork) { expectBlockedStatusCallback(expectBlocked, n.network, defaultTimeoutMs) } } Loading
tests/lib/src/com/android/testutils/TestableNetworkCallback.kt 0 → 100644 +343 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2019 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 com.android.testutils import android.net.ConnectivityManager.NetworkCallback import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import com.android.testutils.RecorderCallback.CallbackRecord.Available import com.android.testutils.RecorderCallback.CallbackRecord.BlockedStatus import com.android.testutils.RecorderCallback.CallbackRecord.CapabilitiesChanged import com.android.testutils.RecorderCallback.CallbackRecord.LinkPropertiesChanged import com.android.testutils.RecorderCallback.CallbackRecord.Lost import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail object NULL_NETWORK : Network(-1) private val Int.capabilityName get() = NetworkCapabilities.capabilityNameOf(this) open class RecorderCallback : NetworkCallback() { sealed class CallbackRecord { // To get equals(), hashcode(), componentN() etc for free, the child classes of // this class are data classes. But while data classes can inherit from other classes, // they may only have visible members in the constructors, so they couldn't declare // a constructor with a non-val arg to pass to CallbackRecord. Instead, force all // subclasses to implement a `network' property, which can be done in a data class // constructor by specifying override. abstract val network: Network data class Available(override val network: Network) : CallbackRecord() data class CapabilitiesChanged( override val network: Network, val caps: NetworkCapabilities ) : CallbackRecord() data class LinkPropertiesChanged( override val network: Network, val lp: LinkProperties ) : CallbackRecord() data class Suspended(override val network: Network) : CallbackRecord() data class Resumed(override val network: Network) : CallbackRecord() data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackRecord() data class Lost(override val network: Network) : CallbackRecord() data class Unavailable private constructor( override val network: Network ) : CallbackRecord() { constructor() : this(NULL_NETWORK) } data class BlockedStatus( override val network: Network, val blocked: Boolean ) : CallbackRecord() // Convenience constants for expecting a type companion object { @JvmField val AVAILABLE = Available::class @JvmField val NETWORK_CAPS_UPDATED = CapabilitiesChanged::class @JvmField val LINK_PROPERTIES_CHANGED = LinkPropertiesChanged::class @JvmField val SUSPENDED = Suspended::class @JvmField val RESUMED = Resumed::class @JvmField val LOSING = Losing::class @JvmField val LOST = Lost::class @JvmField val UNAVAILABLE = Unavailable::class @JvmField val BLOCKED_STATUS = BlockedStatus::class } } protected val history = ArrayTrackRecord<CallbackRecord>().newReadHead() override fun onAvailable(network: Network) { history.add(Available(network)) } override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) { history.add(CapabilitiesChanged(network, caps)) } override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) { history.add(LinkPropertiesChanged(network, lp)) } override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { history.add(BlockedStatus(network, blocked)) } override fun onNetworkSuspended(network: Network) { history.add(CallbackRecord.Suspended(network)) } override fun onNetworkResumed(network: Network) { history.add(CallbackRecord.Resumed(network)) } override fun onLosing(network: Network, maxMsToLive: Int) { history.add(CallbackRecord.Losing(network, maxMsToLive)) } override fun onLost(network: Network) { history.add(CallbackRecord.Lost(network)) } override fun onUnavailable() { history.add(CallbackRecord.Unavailable()) } } typealias CallbackType = KClass<out RecorderCallback.CallbackRecord> const val DEFAULT_TIMEOUT = 200L // ms open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) : RecorderCallback() { // The last available network. Null if the last available network was lost since. val lastAvailableNetwork: Network? get() = when (val it = history.lastOrNull { it is Available || it is Lost }) { is Available -> it.network else -> null } fun pollForNextCallback(timeoutMs: Long = defaultTimeoutMs): CallbackRecord { return history.poll(timeoutMs) ?: fail("Did not receive callback after ${timeoutMs}ms") } // Make open for use in ConnectivityServiceTest which is the only one knowing its handlers. @JvmOverloads open fun assertNoCallback(timeoutMs: Long = defaultTimeoutMs) { val cb = history.poll(timeoutMs) if (null != cb) fail("Expected no callback but got $cb") } inline fun <reified T : CallbackRecord> expectCallback( network: Network, timeoutMs: Long = defaultTimeoutMs ): T = pollForNextCallback(timeoutMs).let { if (it !is T || it.network != network) { fail("Unexpected callback : $it, expected ${T::class} with Network[$network]") } else { it } } fun expectCallbackThat( timeoutMs: Long = defaultTimeoutMs, valid: (CallbackRecord) -> Boolean ) = pollForNextCallback(timeoutMs).also { assertTrue(valid(it), "Unexpected callback : $it") } fun expectCapabilitiesThat( net: Network, tmt: Long = defaultTimeoutMs, valid: (NetworkCapabilities) -> Boolean ): CapabilitiesChanged { return expectCallback<CapabilitiesChanged>(net, tmt).also { assertTrue(valid(it.caps), "Capabilities don't match expectations ${it.caps}") } } fun expectLinkPropertiesThat( net: Network, tmt: Long = defaultTimeoutMs, valid: (LinkProperties) -> Boolean ): LinkPropertiesChanged { return expectCallback<LinkPropertiesChanged>(net, tmt).also { assertTrue(valid(it.lp), "LinkProperties don't match expectations ${it.lp}") } } // Expects onAvailable and the callbacks that follow it. These are: // - onSuspended, iff the network was suspended when the callbacks fire. // - onCapabilitiesChanged. // - onLinkPropertiesChanged. // - onBlockedStatusChanged. // // @param network the network to expect the callbacks on. // @param suspended whether to expect a SUSPENDED callback. // @param validated the expected value of the VALIDATED capability in the // onCapabilitiesChanged callback. // @param tmt how long to wait for the callbacks. fun expectAvailableCallbacks( net: Network, suspended: Boolean = false, validated: Boolean = true, blocked: Boolean = false, tmt: Long = defaultTimeoutMs ) { expectCallback<Available>(net, tmt) if (suspended) { expectCallback<CallbackRecord.Suspended>(net, tmt) } expectCapabilitiesThat(net, tmt) { validated == it.hasCapability(NET_CAPABILITY_VALIDATED) } expectCallback<LinkPropertiesChanged>(net, tmt) expectBlockedStatusCallback(blocked, net) } // Backward compatibility for existing Java code. Use named arguments instead and remove all // these when there is no user left. fun expectAvailableAndSuspendedCallbacks( net: Network, validated: Boolean, tmt: Long = defaultTimeoutMs ) = expectAvailableCallbacks(net, suspended = true, validated = validated, tmt = tmt) fun expectBlockedStatusCallback(blocked: Boolean, net: Network, tmt: Long = defaultTimeoutMs) { expectCallback<BlockedStatus>(net, tmt).also { assertEquals(it.blocked, blocked, "Unexpected blocked status ${it.blocked}") } } // Expects the available callbacks (where the onCapabilitiesChanged must contain the // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the // one we just sent. // TODO: this is likely a bug. Fix it and remove this method. fun expectAvailableDoubleValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) { val mark = history.mark expectAvailableCallbacks(net, tmt = tmt) val firstCaps = history.poll(tmt, mark) { it is CapabilitiesChanged } assertEquals(firstCaps, expectCallback<CapabilitiesChanged>(net, tmt)) } // Expects the available callbacks where the onCapabilitiesChanged must not have validated, // then expects another onCapabilitiesChanged that has the validated bit set. This is used // when a network connects and satisfies a callback, and then immediately validates. fun expectAvailableThenValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) { expectAvailableCallbacks(net, validated = false, tmt = tmt) expectCapabilitiesThat(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) } } // Temporary Java compat measure : have MockNetworkAgent implement this so that all existing // calls with networkAgent can be routed through here without moving MockNetworkAgent. // TODO: clean this up, remove this method. interface HasNetwork { val network: Network } @JvmOverloads open fun <T : CallbackRecord> expectCallback( type: KClass<T>, n: HasNetwork?, timeoutMs: Long = defaultTimeoutMs ) = pollForNextCallback(timeoutMs).also { val network = n?.network ?: NULL_NETWORK // TODO : remove this .java access if the tests ever use kotlin-reflect. At the time of // this writing this would be the only use of this library in the tests. assertTrue(type.java.isInstance(it) && it.network == network, "Unexpected callback : $it, expected ${type.java} with Network[$network]") } as T fun expectAvailableCallbacks( n: HasNetwork, suspended: Boolean, validated: Boolean, blocked: Boolean, timeoutMs: Long ) = expectAvailableCallbacks(n.network, suspended, validated, blocked, timeoutMs) fun expectAvailableAndSuspendedCallbacks(n: HasNetwork, expectValidated: Boolean) { expectAvailableAndSuspendedCallbacks(n.network, expectValidated) } fun expectAvailableCallbacksValidated(n: HasNetwork) { expectAvailableCallbacks(n.network) } fun expectAvailableCallbacksValidatedAndBlocked(n: HasNetwork) { expectAvailableCallbacks(n.network, blocked = true) } fun expectAvailableCallbacksUnvalidated(n: HasNetwork) { expectAvailableCallbacks(n.network, validated = false) } fun expectAvailableCallbacksUnvalidatedAndBlocked(n: HasNetwork) { expectAvailableCallbacks(n.network, validated = false, blocked = true) } fun expectAvailableDoubleValidatedCallbacks(n: HasNetwork) { expectAvailableDoubleValidatedCallbacks(n.network, defaultTimeoutMs) } fun expectAvailableThenValidatedCallbacks(n: HasNetwork) { expectAvailableThenValidatedCallbacks(n.network, defaultTimeoutMs) } @JvmOverloads fun expectLinkPropertiesThat( n: HasNetwork, tmt: Long = defaultTimeoutMs, valid: (LinkProperties) -> Boolean ) = expectLinkPropertiesThat(n.network, tmt, valid) @JvmOverloads fun expectCapabilitiesThat( n: HasNetwork, tmt: Long = defaultTimeoutMs, valid: (NetworkCapabilities) -> Boolean ) = expectCapabilitiesThat(n.network, tmt, valid) @JvmOverloads fun expectCapabilitiesWith( capability: Int, n: HasNetwork, timeoutMs: Long = defaultTimeoutMs ): NetworkCapabilities { return expectCapabilitiesThat(n.network, timeoutMs) { it.hasCapability(capability) }.caps } @JvmOverloads fun expectCapabilitiesWithout( capability: Int, n: HasNetwork, timeoutMs: Long = defaultTimeoutMs ): NetworkCapabilities { return expectCapabilitiesThat(n.network, timeoutMs) { !it.hasCapability(capability) }.caps } fun expectBlockedStatusCallback(expectBlocked: Boolean, n: HasNetwork) { expectBlockedStatusCallback(expectBlocked, n.network, defaultTimeoutMs) } }