Loading tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt +15 −2 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import android.os.SystemClock import java.util.concurrent.CyclicBarrier import kotlin.system.measureTimeMillis import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertNull import kotlin.test.assertTrue Loading Loading @@ -64,7 +65,15 @@ open class ConcurrentIntepreter<T>( // Spins as many threads as needed by the test spec and interpret each program concurrently, // having all threads waiting on a CyclicBarrier after each line. fun interpretTestSpec(spec: String, initial: T, threadTransform: (T) -> T = { it }) { // |lineShift| says how many lines after the call the spec starts. This is used for error // reporting. Unfortunately AFAICT there is no way to get the line of an argument rather // than the line at which the expression starts. fun interpretTestSpec( spec: String, initial: T, lineShift: Int = 0, threadTransform: (T) -> T = { it } ) { // For nice stack traces val callSite = getCallingMethod() val lines = spec.trim().trim('\n').split("\n").map { it.split("|") } Loading @@ -91,7 +100,8 @@ open class ConcurrentIntepreter<T>( // testing. Instead, catch the exception, cancel other threads, and report // nicely. Catch throwable because fail() is AssertionError, which inherits // from Error. crash = InterpretException(threadIndex, it, callSite.lineNumber + lineNum, crash = InterpretException(threadIndex, it, callSite.lineNumber + lineNum + lineShift, callSite.className, callSite.methodName, callSite.fileName, e) } barrier.await() Loading Loading @@ -147,6 +157,9 @@ private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>( // Interpret sleep. Optional argument for the count, in INTERPRET_TIME_UNIT units. Regex("""sleep(\((\d+)\))?""") to { i, t, r -> SystemClock.sleep(if (r.strArg(2).isEmpty()) i.interpretTimeUnit else r.timeArg(2)) }, Regex("""(.*)\s*fails""") to { i, t, r -> assertFails { i.interpret(r.strArg(1), t) } } ) Loading tests/lib/src/com/android/testutils/TestableNetworkCallback.kt +52 −33 Original line number Diff line number Diff line Loading @@ -21,11 +21,15 @@ 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 com.android.testutils.RecorderCallback.CallbackEntry.Available import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged import com.android.testutils.RecorderCallback.CallbackEntry.Losing import com.android.testutils.RecorderCallback.CallbackEntry.Lost import com.android.testutils.RecorderCallback.CallbackEntry.Resumed import com.android.testutils.RecorderCallback.CallbackEntry.Suspended import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertTrue Loading @@ -35,38 +39,43 @@ object NULL_NETWORK : Network(-1) private val Int.capabilityName get() = NetworkCapabilities.capabilityNameOf(this) open class RecorderCallback : NetworkCallback() { sealed class CallbackRecord { open class RecorderCallback private constructor( private val backingRecord: ArrayTrackRecord<CallbackEntry> ) : NetworkCallback() { public constructor() : this(ArrayTrackRecord()) protected constructor(src: RecorderCallback?): this(src?.backingRecord ?: ArrayTrackRecord()) sealed class CallbackEntry { // 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 // a constructor with a non-val arg to pass to CallbackEntry. 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 Available(override val network: Network) : CallbackEntry() data class CapabilitiesChanged( override val network: Network, val caps: NetworkCapabilities ) : CallbackRecord() ) : CallbackEntry() 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() ) : CallbackEntry() data class Suspended(override val network: Network) : CallbackEntry() data class Resumed(override val network: Network) : CallbackEntry() data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackEntry() data class Lost(override val network: Network) : CallbackEntry() data class Unavailable private constructor( override val network: Network ) : CallbackRecord() { ) : CallbackEntry() { constructor() : this(NULL_NETWORK) } data class BlockedStatus( override val network: Network, val blocked: Boolean ) : CallbackRecord() ) : CallbackEntry() // Convenience constants for expecting a type companion object { Loading @@ -91,12 +100,15 @@ open class RecorderCallback : NetworkCallback() { } } protected val history = ArrayTrackRecord<CallbackRecord>().newReadHead() protected val history = backingRecord.newReadHead() override fun onAvailable(network: Network) { history.add(Available(network)) } // PreCheck is not used in the tests today. For backward compatibility with existing tests that // expect the callbacks not to record this, do not listen to PreCheck here. override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) { history.add(CapabilitiesChanged(network, caps)) } Loading @@ -110,39 +122,46 @@ open class RecorderCallback : NetworkCallback() { } override fun onNetworkSuspended(network: Network) { history.add(CallbackRecord.Suspended(network)) history.add(Suspended(network)) } override fun onNetworkResumed(network: Network) { history.add(CallbackRecord.Resumed(network)) history.add(Resumed(network)) } override fun onLosing(network: Network, maxMsToLive: Int) { history.add(CallbackRecord.Losing(network, maxMsToLive)) history.add(Losing(network, maxMsToLive)) } override fun onLost(network: Network) { history.add(CallbackRecord.Lost(network)) history.add(Lost(network)) } override fun onUnavailable() { history.add(CallbackRecord.Unavailable()) history.add(Unavailable()) } } typealias CallbackType = KClass<out RecorderCallback.CallbackRecord> const val DEFAULT_TIMEOUT = 200L // ms private const val DEFAULT_TIMEOUT = 200L // ms open class TestableNetworkCallback private constructor( src: TestableNetworkCallback?, val defaultTimeoutMs: Long = DEFAULT_TIMEOUT ) : RecorderCallback(src) { @JvmOverloads constructor(timeoutMs: Long = DEFAULT_TIMEOUT): this(null, timeoutMs) fun createLinkedCopy() = TestableNetworkCallback(this, defaultTimeoutMs) open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) : RecorderCallback() { // The last available network. Null if the last available network was lost since. // The last available network, or null if any network was lost since the last call to // onAvailable. TODO : fix this by fixing the tests that rely on this behavior 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 { fun pollForNextCallback(timeoutMs: Long = defaultTimeoutMs): CallbackEntry { return history.poll(timeoutMs) ?: fail("Did not receive callback after ${timeoutMs}ms") } Loading @@ -153,7 +172,7 @@ open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) if (null != cb) fail("Expected no callback but got $cb") } inline fun <reified T : CallbackRecord> expectCallback( inline fun <reified T : CallbackEntry> expectCallback( network: Network, timeoutMs: Long = defaultTimeoutMs ): T = pollForNextCallback(timeoutMs).let { Loading @@ -166,7 +185,7 @@ open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) fun expectCallbackThat( timeoutMs: Long = defaultTimeoutMs, valid: (CallbackRecord) -> Boolean valid: (CallbackEntry) -> Boolean ) = pollForNextCallback(timeoutMs).also { assertTrue(valid(it), "Unexpected callback : $it") } fun expectCapabilitiesThat( Loading Loading @@ -209,7 +228,7 @@ open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) ) { expectCallback<Available>(net, tmt) if (suspended) { expectCallback<CallbackRecord.Suspended>(net, tmt) expectCallback<CallbackEntry.Suspended>(net, tmt) } expectCapabilitiesThat(net, tmt) { validated == it.hasCapability(NET_CAPABILITY_VALIDATED) } expectCallback<LinkPropertiesChanged>(net, tmt) Loading Loading @@ -257,7 +276,7 @@ open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) } @JvmOverloads open fun <T : CallbackRecord> expectCallback( open fun <T : CallbackEntry> expectCallback( type: KClass<T>, n: HasNetwork?, timeoutMs: Long = defaultTimeoutMs Loading tests/unit/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ android_test { resource_dirs: ["res"], static_libs: [ "androidx.test.rules", "kotlin-reflect", "mockito-target-extended-minus-junit4", "net-tests-utils", "NetworkStackApiCurrentLib", Loading tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt 0 → 100644 +293 −0 Original line number Diff line number Diff line package android.net.testutils import android.net.LinkAddress import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities import com.android.testutils.ConcurrentIntepreter import com.android.testutils.InterpretMatcher import com.android.testutils.RecorderCallback.CallbackEntry import com.android.testutils.RecorderCallback.CallbackEntry.Available import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged import com.android.testutils.TestableNetworkCallback import com.android.testutils.intArg import com.android.testutils.strArg import com.android.testutils.timeArg import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.test.fail const val SHORT_TIMEOUT_MS = 20L const val DEFAULT_LINGER_DELAY_MS = 30000 const val NOT_METERED = NetworkCapabilities.NET_CAPABILITY_NOT_METERED const val WIFI = NetworkCapabilities.TRANSPORT_WIFI const val CELLULAR = NetworkCapabilities.TRANSPORT_CELLULAR const val TEST_INTERFACE_NAME = "testInterfaceName" @RunWith(JUnit4::class) class TestableNetworkCallbackTest { private lateinit var mCallback: TestableNetworkCallback private fun makeHasNetwork(netId: Int) = object : TestableNetworkCallback.HasNetwork { override val network: Network = Network(netId) } @Before fun setUp() { mCallback = TestableNetworkCallback() } @Test fun testLastAvailableNetwork() { // Make sure there is no last available network at first, then the last available network // is returned after onAvailable is called. val net2097 = Network(2097) assertNull(mCallback.lastAvailableNetwork) mCallback.onAvailable(net2097) assertEquals(mCallback.lastAvailableNetwork, net2097) // Make sure calling onCapsChanged/onLinkPropertiesChanged don't affect the last available // network. mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities()) mCallback.onLinkPropertiesChanged(net2097, LinkProperties()) assertEquals(mCallback.lastAvailableNetwork, net2097) // Make sure onLost clears the last available network. mCallback.onLost(net2097) assertNull(mCallback.lastAvailableNetwork) // Do the same but with a different network after onLost : make sure the last available // network is the new one, not the original one. val net2098 = Network(2098) mCallback.onAvailable(net2098) mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities()) mCallback.onLinkPropertiesChanged(net2098, LinkProperties()) assertEquals(mCallback.lastAvailableNetwork, net2098) // Make sure onAvailable changes the last available network even if onLost was not called. val net2099 = Network(2099) mCallback.onAvailable(net2099) assertEquals(mCallback.lastAvailableNetwork, net2099) // For legacy reasons, lastAvailableNetwork is null as soon as any is lost, not necessarily // the last available one. Check that behavior. mCallback.onLost(net2098) assertNull(mCallback.lastAvailableNetwork) // Make sure that losing the really last available one still results in null. mCallback.onLost(net2099) assertNull(mCallback.lastAvailableNetwork) // Make sure multiple onAvailable in a row then onLost still results in null. mCallback.onAvailable(net2097) mCallback.onAvailable(net2098) mCallback.onAvailable(net2099) mCallback.onLost(net2097) assertNull(mCallback.lastAvailableNetwork) } @Test fun testAssertNoCallback() { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) mCallback.onAvailable(Network(100)) assertFails { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) } } @Test fun testCapabilitiesWithAndWithout() { val net = Network(101) val matcher = makeHasNetwork(101) val meteredNc = NetworkCapabilities() val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED) // Check that expecting caps (with or without) fails when no callback has been received. assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } // Add NOT_METERED and check that With succeeds and Without fails. mCallback.onCapabilitiesChanged(net, unmeteredNc) mCallback.expectCapabilitiesWith(NOT_METERED, matcher) mCallback.onCapabilitiesChanged(net, unmeteredNc) assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } // Don't add NOT_METERED and check that With fails and Without succeeds. mCallback.onCapabilitiesChanged(net, meteredNc) assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } mCallback.onCapabilitiesChanged(net, meteredNc) mCallback.expectCapabilitiesWithout(NOT_METERED, matcher) } @Test fun testExpectCallbackThat() { val net = Network(193) val netCaps = NetworkCapabilities().addTransportType(CELLULAR) // Check that expecting callbackThat anything fails when no callback has been received. assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { true } } // Basic test for true and false mCallback.onAvailable(net) mCallback.expectCallbackThat { true } mCallback.onAvailable(net) assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { false } } // Try a positive and a negative case mCallback.onBlockedStatusChanged(net, true) mCallback.expectCallbackThat { cb -> cb is BlockedStatus && cb.blocked } mCallback.onCapabilitiesChanged(net, netCaps) assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { cb -> cb is CapabilitiesChanged && cb.caps.hasTransport(WIFI) } } } @Test fun testCapabilitiesThat() { val net = Network(101) val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI) // Check that expecting capabilitiesThat anything fails when no callback has been received. assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { true } } // Basic test for true and false mCallback.onCapabilitiesChanged(net, netCaps) mCallback.expectCapabilitiesThat(net) { true } mCallback.onCapabilitiesChanged(net, netCaps) assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { false } } // Try a positive and a negative case mCallback.onCapabilitiesChanged(net, netCaps) mCallback.expectCapabilitiesThat(net) { caps -> caps.hasCapability(NOT_METERED) && caps.hasTransport(WIFI) && !caps.hasTransport(CELLULAR) } mCallback.onCapabilitiesChanged(net, netCaps) assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { caps -> caps.hasTransport(CELLULAR) } } // Try a matching callback on the wrong network mCallback.onCapabilitiesChanged(net, netCaps) assertFails { mCallback.expectCapabilitiesThat(Network(100), SHORT_TIMEOUT_MS) { true } } } @Test fun testLinkPropertiesThat() { val net = Network(112) val linkAddress = LinkAddress("fe80::ace:d00d/64") val mtu = 1984 val linkProps = LinkProperties().apply { this.mtu = mtu interfaceName = TEST_INTERFACE_NAME addLinkAddress(linkAddress) } // Check that expecting linkPropsThat anything fails when no callback has been received. assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { true } } // Basic test for true and false mCallback.onLinkPropertiesChanged(net, linkProps) mCallback.expectLinkPropertiesThat(net) { true } mCallback.onLinkPropertiesChanged(net, linkProps) assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { false } } // Try a positive and negative case mCallback.onLinkPropertiesChanged(net, linkProps) mCallback.expectLinkPropertiesThat(net) { lp -> lp.interfaceName == TEST_INTERFACE_NAME && lp.linkAddresses.contains(linkAddress) && lp.mtu == mtu } mCallback.onLinkPropertiesChanged(net, linkProps) assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { lp -> lp.interfaceName != TEST_INTERFACE_NAME } } // Try a matching callback on the wrong network mCallback.onLinkPropertiesChanged(net, linkProps) assertFails { mCallback.expectLinkPropertiesThat(Network(114), SHORT_TIMEOUT_MS) { lp -> lp.interfaceName == TEST_INTERFACE_NAME } } } @Test fun testExpectCallback() { val net = Network(103) // Test expectCallback fails when nothing was sent. assertFails { mCallback.expectCallback<BlockedStatus>(net, SHORT_TIMEOUT_MS) } // Test onAvailable is seen and can be expected mCallback.onAvailable(net) mCallback.expectCallback<Available>(net, SHORT_TIMEOUT_MS) // Test onAvailable won't return calls with a different network mCallback.onAvailable(Network(106)) assertFails { mCallback.expectCallback<Available>(net, SHORT_TIMEOUT_MS) } // Test onAvailable won't return calls with a different callback mCallback.onAvailable(net) assertFails { mCallback.expectCallback<BlockedStatus>(net, SHORT_TIMEOUT_MS) } } @Test fun testPollForNextCallback() { assertFails { mCallback.pollForNextCallback(SHORT_TIMEOUT_MS) } TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1, threadTransform = { cb -> cb.createLinkedCopy() }, spec = """ sleep; onAvailable(133) | poll(2) = Available(133) time 1..4 | poll(1) fails onCapabilitiesChanged(108) | poll(1) = CapabilitiesChanged(108) time 0..3 onBlockedStatus(199) | poll(1) = BlockedStatus(199) time 0..3 """) } } private object TNCInterpreter : ConcurrentIntepreter<TestableNetworkCallback>(interpretTable) val EntryList = CallbackEntry::class.sealedSubclasses.map { it.simpleName }.joinToString("|") private fun callbackEntryFromString(name: String): KClass<out CallbackEntry> { return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name } } private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>( // Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for // all callback types. This is implemented above by enumerating the subclasses of // CallbackEntry and reading their simpleName. Regex("""(.*)\s+=\s+($EntryList)\((\d+)\)""") to { i, cb, t -> val record = i.interpret(t.strArg(1), cb) assertTrue(callbackEntryFromString(t.strArg(2)).isInstance(record)) // Strictly speaking testing for is CallbackEntry is useless as it's been tested above // but the compiler can't figure things out from the isInstance call. It does understand // from the assertTrue(is CallbackEntry) that this is true, which allows to access // the 'network' member below. assertTrue(record is CallbackEntry) assertEquals(record.network.netId, t.intArg(3)) }, // Interpret "onAvailable(xx)" as calling "onAvailable" with a netId of xx, and likewise for // all callback types. NetworkCapabilities and LinkProperties just get an empty object // as their argument. Losing gets the default linger timer. Blocked gets false. Regex("""on($EntryList)\((\d+)\)""") to { i, cb, t -> val net = Network(t.intArg(2)) when (t.strArg(1)) { "Available" -> cb.onAvailable(net) // PreCheck not used in tests. Add it here if it becomes useful. "CapabilitiesChanged" -> cb.onCapabilitiesChanged(net, NetworkCapabilities()) "LinkPropertiesChanged" -> cb.onLinkPropertiesChanged(net, LinkProperties()) "Suspended" -> cb.onNetworkSuspended(net) "Resumed" -> cb.onNetworkResumed(net) "Losing" -> cb.onLosing(net, DEFAULT_LINGER_DELAY_MS) "Lost" -> cb.onLost(net) "Unavailable" -> cb.onUnavailable() "BlockedStatus" -> cb.onBlockedStatusChanged(net, false) else -> fail("Unknown callback type") } }, Regex("""poll\((\d+)\)""") to { i, cb, t -> cb.pollForNextCallback(t.timeArg(1)) } ) tests/unit/src/android/net/testutils/TrackRecordTest.kt +2 −1 Original line number Diff line number Diff line Loading @@ -352,7 +352,8 @@ class TrackRecordTest { private object TRTInterpreter : ConcurrentIntepreter<TrackRecord<Int>>(interpretTable) { fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) { interpretTestSpec(spec, ArrayTrackRecord(), { (it as ArrayTrackRecord).newReadHead() }) interpretTestSpec(spec, initial = ArrayTrackRecord(), threadTransform = { (it as ArrayTrackRecord).newReadHead() }) } else { interpretTestSpec(spec, ArrayTrackRecord()) } Loading Loading
tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt +15 −2 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import android.os.SystemClock import java.util.concurrent.CyclicBarrier import kotlin.system.measureTimeMillis import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertNull import kotlin.test.assertTrue Loading Loading @@ -64,7 +65,15 @@ open class ConcurrentIntepreter<T>( // Spins as many threads as needed by the test spec and interpret each program concurrently, // having all threads waiting on a CyclicBarrier after each line. fun interpretTestSpec(spec: String, initial: T, threadTransform: (T) -> T = { it }) { // |lineShift| says how many lines after the call the spec starts. This is used for error // reporting. Unfortunately AFAICT there is no way to get the line of an argument rather // than the line at which the expression starts. fun interpretTestSpec( spec: String, initial: T, lineShift: Int = 0, threadTransform: (T) -> T = { it } ) { // For nice stack traces val callSite = getCallingMethod() val lines = spec.trim().trim('\n').split("\n").map { it.split("|") } Loading @@ -91,7 +100,8 @@ open class ConcurrentIntepreter<T>( // testing. Instead, catch the exception, cancel other threads, and report // nicely. Catch throwable because fail() is AssertionError, which inherits // from Error. crash = InterpretException(threadIndex, it, callSite.lineNumber + lineNum, crash = InterpretException(threadIndex, it, callSite.lineNumber + lineNum + lineShift, callSite.className, callSite.methodName, callSite.fileName, e) } barrier.await() Loading Loading @@ -147,6 +157,9 @@ private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>( // Interpret sleep. Optional argument for the count, in INTERPRET_TIME_UNIT units. Regex("""sleep(\((\d+)\))?""") to { i, t, r -> SystemClock.sleep(if (r.strArg(2).isEmpty()) i.interpretTimeUnit else r.timeArg(2)) }, Regex("""(.*)\s*fails""") to { i, t, r -> assertFails { i.interpret(r.strArg(1), t) } } ) Loading
tests/lib/src/com/android/testutils/TestableNetworkCallback.kt +52 −33 Original line number Diff line number Diff line Loading @@ -21,11 +21,15 @@ 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 com.android.testutils.RecorderCallback.CallbackEntry.Available import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged import com.android.testutils.RecorderCallback.CallbackEntry.Losing import com.android.testutils.RecorderCallback.CallbackEntry.Lost import com.android.testutils.RecorderCallback.CallbackEntry.Resumed import com.android.testutils.RecorderCallback.CallbackEntry.Suspended import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertTrue Loading @@ -35,38 +39,43 @@ object NULL_NETWORK : Network(-1) private val Int.capabilityName get() = NetworkCapabilities.capabilityNameOf(this) open class RecorderCallback : NetworkCallback() { sealed class CallbackRecord { open class RecorderCallback private constructor( private val backingRecord: ArrayTrackRecord<CallbackEntry> ) : NetworkCallback() { public constructor() : this(ArrayTrackRecord()) protected constructor(src: RecorderCallback?): this(src?.backingRecord ?: ArrayTrackRecord()) sealed class CallbackEntry { // 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 // a constructor with a non-val arg to pass to CallbackEntry. 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 Available(override val network: Network) : CallbackEntry() data class CapabilitiesChanged( override val network: Network, val caps: NetworkCapabilities ) : CallbackRecord() ) : CallbackEntry() 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() ) : CallbackEntry() data class Suspended(override val network: Network) : CallbackEntry() data class Resumed(override val network: Network) : CallbackEntry() data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackEntry() data class Lost(override val network: Network) : CallbackEntry() data class Unavailable private constructor( override val network: Network ) : CallbackRecord() { ) : CallbackEntry() { constructor() : this(NULL_NETWORK) } data class BlockedStatus( override val network: Network, val blocked: Boolean ) : CallbackRecord() ) : CallbackEntry() // Convenience constants for expecting a type companion object { Loading @@ -91,12 +100,15 @@ open class RecorderCallback : NetworkCallback() { } } protected val history = ArrayTrackRecord<CallbackRecord>().newReadHead() protected val history = backingRecord.newReadHead() override fun onAvailable(network: Network) { history.add(Available(network)) } // PreCheck is not used in the tests today. For backward compatibility with existing tests that // expect the callbacks not to record this, do not listen to PreCheck here. override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) { history.add(CapabilitiesChanged(network, caps)) } Loading @@ -110,39 +122,46 @@ open class RecorderCallback : NetworkCallback() { } override fun onNetworkSuspended(network: Network) { history.add(CallbackRecord.Suspended(network)) history.add(Suspended(network)) } override fun onNetworkResumed(network: Network) { history.add(CallbackRecord.Resumed(network)) history.add(Resumed(network)) } override fun onLosing(network: Network, maxMsToLive: Int) { history.add(CallbackRecord.Losing(network, maxMsToLive)) history.add(Losing(network, maxMsToLive)) } override fun onLost(network: Network) { history.add(CallbackRecord.Lost(network)) history.add(Lost(network)) } override fun onUnavailable() { history.add(CallbackRecord.Unavailable()) history.add(Unavailable()) } } typealias CallbackType = KClass<out RecorderCallback.CallbackRecord> const val DEFAULT_TIMEOUT = 200L // ms private const val DEFAULT_TIMEOUT = 200L // ms open class TestableNetworkCallback private constructor( src: TestableNetworkCallback?, val defaultTimeoutMs: Long = DEFAULT_TIMEOUT ) : RecorderCallback(src) { @JvmOverloads constructor(timeoutMs: Long = DEFAULT_TIMEOUT): this(null, timeoutMs) fun createLinkedCopy() = TestableNetworkCallback(this, defaultTimeoutMs) open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) : RecorderCallback() { // The last available network. Null if the last available network was lost since. // The last available network, or null if any network was lost since the last call to // onAvailable. TODO : fix this by fixing the tests that rely on this behavior 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 { fun pollForNextCallback(timeoutMs: Long = defaultTimeoutMs): CallbackEntry { return history.poll(timeoutMs) ?: fail("Did not receive callback after ${timeoutMs}ms") } Loading @@ -153,7 +172,7 @@ open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) if (null != cb) fail("Expected no callback but got $cb") } inline fun <reified T : CallbackRecord> expectCallback( inline fun <reified T : CallbackEntry> expectCallback( network: Network, timeoutMs: Long = defaultTimeoutMs ): T = pollForNextCallback(timeoutMs).let { Loading @@ -166,7 +185,7 @@ open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) fun expectCallbackThat( timeoutMs: Long = defaultTimeoutMs, valid: (CallbackRecord) -> Boolean valid: (CallbackEntry) -> Boolean ) = pollForNextCallback(timeoutMs).also { assertTrue(valid(it), "Unexpected callback : $it") } fun expectCapabilitiesThat( Loading Loading @@ -209,7 +228,7 @@ open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) ) { expectCallback<Available>(net, tmt) if (suspended) { expectCallback<CallbackRecord.Suspended>(net, tmt) expectCallback<CallbackEntry.Suspended>(net, tmt) } expectCapabilitiesThat(net, tmt) { validated == it.hasCapability(NET_CAPABILITY_VALIDATED) } expectCallback<LinkPropertiesChanged>(net, tmt) Loading Loading @@ -257,7 +276,7 @@ open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT) } @JvmOverloads open fun <T : CallbackRecord> expectCallback( open fun <T : CallbackEntry> expectCallback( type: KClass<T>, n: HasNetwork?, timeoutMs: Long = defaultTimeoutMs Loading
tests/unit/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ android_test { resource_dirs: ["res"], static_libs: [ "androidx.test.rules", "kotlin-reflect", "mockito-target-extended-minus-junit4", "net-tests-utils", "NetworkStackApiCurrentLib", Loading
tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt 0 → 100644 +293 −0 Original line number Diff line number Diff line package android.net.testutils import android.net.LinkAddress import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities import com.android.testutils.ConcurrentIntepreter import com.android.testutils.InterpretMatcher import com.android.testutils.RecorderCallback.CallbackEntry import com.android.testutils.RecorderCallback.CallbackEntry.Available import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged import com.android.testutils.TestableNetworkCallback import com.android.testutils.intArg import com.android.testutils.strArg import com.android.testutils.timeArg import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.test.fail const val SHORT_TIMEOUT_MS = 20L const val DEFAULT_LINGER_DELAY_MS = 30000 const val NOT_METERED = NetworkCapabilities.NET_CAPABILITY_NOT_METERED const val WIFI = NetworkCapabilities.TRANSPORT_WIFI const val CELLULAR = NetworkCapabilities.TRANSPORT_CELLULAR const val TEST_INTERFACE_NAME = "testInterfaceName" @RunWith(JUnit4::class) class TestableNetworkCallbackTest { private lateinit var mCallback: TestableNetworkCallback private fun makeHasNetwork(netId: Int) = object : TestableNetworkCallback.HasNetwork { override val network: Network = Network(netId) } @Before fun setUp() { mCallback = TestableNetworkCallback() } @Test fun testLastAvailableNetwork() { // Make sure there is no last available network at first, then the last available network // is returned after onAvailable is called. val net2097 = Network(2097) assertNull(mCallback.lastAvailableNetwork) mCallback.onAvailable(net2097) assertEquals(mCallback.lastAvailableNetwork, net2097) // Make sure calling onCapsChanged/onLinkPropertiesChanged don't affect the last available // network. mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities()) mCallback.onLinkPropertiesChanged(net2097, LinkProperties()) assertEquals(mCallback.lastAvailableNetwork, net2097) // Make sure onLost clears the last available network. mCallback.onLost(net2097) assertNull(mCallback.lastAvailableNetwork) // Do the same but with a different network after onLost : make sure the last available // network is the new one, not the original one. val net2098 = Network(2098) mCallback.onAvailable(net2098) mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities()) mCallback.onLinkPropertiesChanged(net2098, LinkProperties()) assertEquals(mCallback.lastAvailableNetwork, net2098) // Make sure onAvailable changes the last available network even if onLost was not called. val net2099 = Network(2099) mCallback.onAvailable(net2099) assertEquals(mCallback.lastAvailableNetwork, net2099) // For legacy reasons, lastAvailableNetwork is null as soon as any is lost, not necessarily // the last available one. Check that behavior. mCallback.onLost(net2098) assertNull(mCallback.lastAvailableNetwork) // Make sure that losing the really last available one still results in null. mCallback.onLost(net2099) assertNull(mCallback.lastAvailableNetwork) // Make sure multiple onAvailable in a row then onLost still results in null. mCallback.onAvailable(net2097) mCallback.onAvailable(net2098) mCallback.onAvailable(net2099) mCallback.onLost(net2097) assertNull(mCallback.lastAvailableNetwork) } @Test fun testAssertNoCallback() { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) mCallback.onAvailable(Network(100)) assertFails { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) } } @Test fun testCapabilitiesWithAndWithout() { val net = Network(101) val matcher = makeHasNetwork(101) val meteredNc = NetworkCapabilities() val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED) // Check that expecting caps (with or without) fails when no callback has been received. assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } // Add NOT_METERED and check that With succeeds and Without fails. mCallback.onCapabilitiesChanged(net, unmeteredNc) mCallback.expectCapabilitiesWith(NOT_METERED, matcher) mCallback.onCapabilitiesChanged(net, unmeteredNc) assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } // Don't add NOT_METERED and check that With fails and Without succeeds. mCallback.onCapabilitiesChanged(net, meteredNc) assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } mCallback.onCapabilitiesChanged(net, meteredNc) mCallback.expectCapabilitiesWithout(NOT_METERED, matcher) } @Test fun testExpectCallbackThat() { val net = Network(193) val netCaps = NetworkCapabilities().addTransportType(CELLULAR) // Check that expecting callbackThat anything fails when no callback has been received. assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { true } } // Basic test for true and false mCallback.onAvailable(net) mCallback.expectCallbackThat { true } mCallback.onAvailable(net) assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { false } } // Try a positive and a negative case mCallback.onBlockedStatusChanged(net, true) mCallback.expectCallbackThat { cb -> cb is BlockedStatus && cb.blocked } mCallback.onCapabilitiesChanged(net, netCaps) assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { cb -> cb is CapabilitiesChanged && cb.caps.hasTransport(WIFI) } } } @Test fun testCapabilitiesThat() { val net = Network(101) val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI) // Check that expecting capabilitiesThat anything fails when no callback has been received. assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { true } } // Basic test for true and false mCallback.onCapabilitiesChanged(net, netCaps) mCallback.expectCapabilitiesThat(net) { true } mCallback.onCapabilitiesChanged(net, netCaps) assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { false } } // Try a positive and a negative case mCallback.onCapabilitiesChanged(net, netCaps) mCallback.expectCapabilitiesThat(net) { caps -> caps.hasCapability(NOT_METERED) && caps.hasTransport(WIFI) && !caps.hasTransport(CELLULAR) } mCallback.onCapabilitiesChanged(net, netCaps) assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { caps -> caps.hasTransport(CELLULAR) } } // Try a matching callback on the wrong network mCallback.onCapabilitiesChanged(net, netCaps) assertFails { mCallback.expectCapabilitiesThat(Network(100), SHORT_TIMEOUT_MS) { true } } } @Test fun testLinkPropertiesThat() { val net = Network(112) val linkAddress = LinkAddress("fe80::ace:d00d/64") val mtu = 1984 val linkProps = LinkProperties().apply { this.mtu = mtu interfaceName = TEST_INTERFACE_NAME addLinkAddress(linkAddress) } // Check that expecting linkPropsThat anything fails when no callback has been received. assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { true } } // Basic test for true and false mCallback.onLinkPropertiesChanged(net, linkProps) mCallback.expectLinkPropertiesThat(net) { true } mCallback.onLinkPropertiesChanged(net, linkProps) assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { false } } // Try a positive and negative case mCallback.onLinkPropertiesChanged(net, linkProps) mCallback.expectLinkPropertiesThat(net) { lp -> lp.interfaceName == TEST_INTERFACE_NAME && lp.linkAddresses.contains(linkAddress) && lp.mtu == mtu } mCallback.onLinkPropertiesChanged(net, linkProps) assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { lp -> lp.interfaceName != TEST_INTERFACE_NAME } } // Try a matching callback on the wrong network mCallback.onLinkPropertiesChanged(net, linkProps) assertFails { mCallback.expectLinkPropertiesThat(Network(114), SHORT_TIMEOUT_MS) { lp -> lp.interfaceName == TEST_INTERFACE_NAME } } } @Test fun testExpectCallback() { val net = Network(103) // Test expectCallback fails when nothing was sent. assertFails { mCallback.expectCallback<BlockedStatus>(net, SHORT_TIMEOUT_MS) } // Test onAvailable is seen and can be expected mCallback.onAvailable(net) mCallback.expectCallback<Available>(net, SHORT_TIMEOUT_MS) // Test onAvailable won't return calls with a different network mCallback.onAvailable(Network(106)) assertFails { mCallback.expectCallback<Available>(net, SHORT_TIMEOUT_MS) } // Test onAvailable won't return calls with a different callback mCallback.onAvailable(net) assertFails { mCallback.expectCallback<BlockedStatus>(net, SHORT_TIMEOUT_MS) } } @Test fun testPollForNextCallback() { assertFails { mCallback.pollForNextCallback(SHORT_TIMEOUT_MS) } TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1, threadTransform = { cb -> cb.createLinkedCopy() }, spec = """ sleep; onAvailable(133) | poll(2) = Available(133) time 1..4 | poll(1) fails onCapabilitiesChanged(108) | poll(1) = CapabilitiesChanged(108) time 0..3 onBlockedStatus(199) | poll(1) = BlockedStatus(199) time 0..3 """) } } private object TNCInterpreter : ConcurrentIntepreter<TestableNetworkCallback>(interpretTable) val EntryList = CallbackEntry::class.sealedSubclasses.map { it.simpleName }.joinToString("|") private fun callbackEntryFromString(name: String): KClass<out CallbackEntry> { return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name } } private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>( // Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for // all callback types. This is implemented above by enumerating the subclasses of // CallbackEntry and reading their simpleName. Regex("""(.*)\s+=\s+($EntryList)\((\d+)\)""") to { i, cb, t -> val record = i.interpret(t.strArg(1), cb) assertTrue(callbackEntryFromString(t.strArg(2)).isInstance(record)) // Strictly speaking testing for is CallbackEntry is useless as it's been tested above // but the compiler can't figure things out from the isInstance call. It does understand // from the assertTrue(is CallbackEntry) that this is true, which allows to access // the 'network' member below. assertTrue(record is CallbackEntry) assertEquals(record.network.netId, t.intArg(3)) }, // Interpret "onAvailable(xx)" as calling "onAvailable" with a netId of xx, and likewise for // all callback types. NetworkCapabilities and LinkProperties just get an empty object // as their argument. Losing gets the default linger timer. Blocked gets false. Regex("""on($EntryList)\((\d+)\)""") to { i, cb, t -> val net = Network(t.intArg(2)) when (t.strArg(1)) { "Available" -> cb.onAvailable(net) // PreCheck not used in tests. Add it here if it becomes useful. "CapabilitiesChanged" -> cb.onCapabilitiesChanged(net, NetworkCapabilities()) "LinkPropertiesChanged" -> cb.onLinkPropertiesChanged(net, LinkProperties()) "Suspended" -> cb.onNetworkSuspended(net) "Resumed" -> cb.onNetworkResumed(net) "Losing" -> cb.onLosing(net, DEFAULT_LINGER_DELAY_MS) "Lost" -> cb.onLost(net) "Unavailable" -> cb.onUnavailable() "BlockedStatus" -> cb.onBlockedStatusChanged(net, false) else -> fail("Unknown callback type") } }, Regex("""poll\((\d+)\)""") to { i, cb, t -> cb.pollForNextCallback(t.timeArg(1)) } )
tests/unit/src/android/net/testutils/TrackRecordTest.kt +2 −1 Original line number Diff line number Diff line Loading @@ -352,7 +352,8 @@ class TrackRecordTest { private object TRTInterpreter : ConcurrentIntepreter<TrackRecord<Int>>(interpretTable) { fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) { interpretTestSpec(spec, ArrayTrackRecord(), { (it as ArrayTrackRecord).newReadHead() }) interpretTestSpec(spec, initial = ArrayTrackRecord(), threadTransform = { (it as ArrayTrackRecord).newReadHead() }) } else { interpretTestSpec(spec, ArrayTrackRecord()) } Loading