Merge changes Iabc2fad0,I2078330b into main
* changes: Improve TestableNetworkOfferCallback Add a way to poll with a position for ReadHead
This commit is contained in:
commit
74dcb4637c
5 changed files with 136 additions and 52 deletions
|
|
@ -23,14 +23,20 @@ import android.util.Log
|
|||
import com.android.net.module.util.ArrayTrackRecord
|
||||
import kotlin.test.fail
|
||||
|
||||
class TestableNetworkOfferCallback(val timeoutMs: Long, private val noCallbackTimeoutMs: Long)
|
||||
: NetworkProvider.NetworkOfferCallback {
|
||||
private const val DEFAULT_TIMEOUT = 30_000L // ms
|
||||
private const val DEFAULT_NO_CALLBACK_TIMEOUT = 200L // ms
|
||||
|
||||
class TestableNetworkOfferCallback(
|
||||
val timeoutMs: Long = DEFAULT_TIMEOUT,
|
||||
private val noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
|
||||
) : NetworkProvider.NetworkOfferCallback {
|
||||
private val TAG = this::class.simpleName
|
||||
val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
|
||||
val mark get() = history.mark
|
||||
|
||||
sealed class CallbackEntry {
|
||||
data class OnNetworkNeeded(val request: NetworkRequest) : CallbackEntry()
|
||||
data class OnNetworkUnneeded(val request: NetworkRequest) : CallbackEntry()
|
||||
data class Needed(val request: NetworkRequest) : CallbackEntry()
|
||||
data class Unneeded(val request: NetworkRequest) : CallbackEntry()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -39,7 +45,7 @@ class TestableNetworkOfferCallback(val timeoutMs: Long, private val noCallbackTi
|
|||
*/
|
||||
override fun onNetworkNeeded(request: NetworkRequest) {
|
||||
Log.d(TAG, "onNetworkNeeded $request")
|
||||
history.add(CallbackEntry.OnNetworkNeeded(request))
|
||||
history.add(CallbackEntry.Needed(request))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -47,25 +53,59 @@ class TestableNetworkOfferCallback(val timeoutMs: Long, private val noCallbackTi
|
|||
*/
|
||||
override fun onNetworkUnneeded(request: NetworkRequest) {
|
||||
Log.d(TAG, "onNetworkUnneeded $request")
|
||||
history.add(CallbackEntry.OnNetworkUnneeded(request))
|
||||
history.add(CallbackEntry.Unneeded(request))
|
||||
}
|
||||
|
||||
// TODO : move to some utility, this is copied from TestableNetworkCallback
|
||||
fun failWithErrorReason(errorMsg: String?, errorReason: String): Nothing {
|
||||
val message = if (errorMsg != null) "$errorMsg : $errorReason" else errorReason
|
||||
fail(message)
|
||||
}
|
||||
|
||||
inline fun <reified T : CallbackEntry> expect(
|
||||
errorMsg: String? = null,
|
||||
predicate: (T) -> Boolean = { true }
|
||||
): T {
|
||||
val nextEntry = history.poll(timeoutMs) ?: failWithErrorReason(errorMsg,
|
||||
"Did not receive ${T::class.simpleName} after ${timeoutMs}ms")
|
||||
if (nextEntry !is T) {
|
||||
failWithErrorReason(
|
||||
errorMsg,
|
||||
"Expected callback ${T::class.simpleName}, got $nextEntry"
|
||||
)
|
||||
}
|
||||
if (!predicate(nextEntry)) {
|
||||
failWithErrorReason(errorMsg, "Callback doesn't match predicate: $nextEntry")
|
||||
}
|
||||
return nextEntry
|
||||
}
|
||||
|
||||
// TODO : remove when there are no callers
|
||||
@Deprecated("Use expect instead of expectCallbackThat")
|
||||
inline fun <reified T : CallbackEntry> expectCallbackThat(
|
||||
crossinline predicate: (T) -> Boolean
|
||||
): T {
|
||||
val event = history.poll(timeoutMs)
|
||||
?: fail("Did not receive callback after ${timeoutMs}ms")
|
||||
if (event !is T || !predicate(event)) fail("Received unexpected callback $event")
|
||||
return event
|
||||
}
|
||||
): T = expect<T>(null, predicate)
|
||||
|
||||
inline fun <reified T : CallbackEntry> eventuallyExpect(
|
||||
errorMsg: String? = null,
|
||||
from: Int = mark,
|
||||
crossinline predicate: (T) -> Boolean = { true }
|
||||
) = history.poll(timeoutMs, from) { it is T && predicate(it) }.also {
|
||||
if (null == it) {
|
||||
failWithErrorReason(
|
||||
errorMsg,
|
||||
"Callback ${T::class} not received within ${timeoutMs}ms."
|
||||
)
|
||||
}
|
||||
} as T
|
||||
|
||||
fun expectOnNetworkNeeded(capabilities: NetworkCapabilities) =
|
||||
expectCallbackThat<CallbackEntry.OnNetworkNeeded> {
|
||||
expectCallbackThat<CallbackEntry.Needed> {
|
||||
it.request.canBeSatisfiedBy(capabilities)
|
||||
}
|
||||
|
||||
fun expectOnNetworkUnneeded(capabilities: NetworkCapabilities) =
|
||||
expectCallbackThat<CallbackEntry.OnNetworkUnneeded> {
|
||||
expectCallbackThat<CallbackEntry.Unneeded> {
|
||||
it.request.canBeSatisfiedBy(capabilities)
|
||||
}
|
||||
|
||||
|
|
@ -73,4 +113,4 @@ class TestableNetworkOfferCallback(val timeoutMs: Long, private val noCallbackTi
|
|||
val cb = history.poll(noCallbackTimeoutMs)
|
||||
if (null != cb) fail("Expected no callback but got $cb")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ class ArrayTrackRecord<E> : TrackRecord<E> {
|
|||
* the last element in the queue if null is returned. This means this method will always
|
||||
* skip elements that do not match the predicate, even if it returns null.
|
||||
*
|
||||
* This method can only be used by the thread that created this ManagedRecordingQueue.
|
||||
* This method can only be used by the thread that created this ReadHead.
|
||||
* If used on another thread, this throws IllegalStateException.
|
||||
*
|
||||
* @param timeoutMs how long, in milliseconds, to wait at most (best effort approximation).
|
||||
|
|
@ -277,6 +277,55 @@ class ArrayTrackRecord<E> : TrackRecord<E> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first element after the mark, optionally blocking until one is available,
|
||||
* or null if no such element can be found within the timeout.
|
||||
* If a predicate is given, only elements matching the predicate are returned.
|
||||
*
|
||||
* Upon return, the mark will be advanced up to the point of the returned element (or
|
||||
* after the last element in the queue if not found) if and only if that element was
|
||||
* found after the current mark. This means this method will always skip elements that
|
||||
* do not match the predicate even if it returns null, and repeated calls with the
|
||||
* same position will advance the mark up to the last of the found elements.
|
||||
*
|
||||
* This method can only be used by the thread that created this ReadHead.
|
||||
* If used on another thread, this throws IllegalStateException.
|
||||
*
|
||||
* Typical usage would be to check for multiple elements in any order. For example,
|
||||
* val mark = readHead.mark
|
||||
* readHead.poll(timeout, mark) { it matches condition 1 }
|
||||
* readHead.poll(timeout, mark) { it matches condition 2 }
|
||||
* readHead.poll(timeout, mark) { it matches condition 3 }
|
||||
* The above would check that starting at the mark, there are three events, one matching
|
||||
* each condition, in any order. A caller could further check that there were no other
|
||||
* event beside the 3 checked with something like
|
||||
* assertEquals(readHead.mark - mark, 3)
|
||||
*
|
||||
* @param timeoutMs how long, in milliseconds, to wait at most (best effort approximation).
|
||||
* @param pos the position in the underlying track record where to start the search.
|
||||
* @param predicate a predicate to filter elements to be returned.
|
||||
* @return an element matching the predicate, or null if timeout.
|
||||
*/
|
||||
override fun poll(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean): E? {
|
||||
val stamp = slock.tryWriteLock()
|
||||
if (0L == stamp) concurrentAccessDetected()
|
||||
pollMark = readHead
|
||||
try {
|
||||
lock.withLock {
|
||||
val index = pollForIndexReadLocked(timeoutMs, pos, predicate)
|
||||
when {
|
||||
index < 0 -> readHead = size
|
||||
index > readHead -> readHead = index + 1
|
||||
// else do nothing, the element was found prior to the read head so
|
||||
// don't move the read head
|
||||
}
|
||||
return getOrNull(index)
|
||||
}
|
||||
} finally {
|
||||
slock.unlockWrite(stamp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of events that were observed since before the last poll() operation on
|
||||
* this ReadHead.
|
||||
|
|
|
|||
|
|
@ -177,10 +177,11 @@ class NetworkValidationTest {
|
|||
cm.registerNetworkCallback(ethRequest, testCb)
|
||||
|
||||
try {
|
||||
val ncCb = testCb.eventuallyExpect<CallbackEntry.CapabilitiesChanged> {
|
||||
val mark = testCb.mark
|
||||
val ncCb = testCb.eventuallyExpect<CallbackEntry.CapabilitiesChanged>(from = mark) {
|
||||
it.caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
|
||||
}
|
||||
testCb.eventuallyExpect<CallbackEntry.LinkPropertiesChanged> {
|
||||
testCb.eventuallyExpect<CallbackEntry.LinkPropertiesChanged>(from = mark) {
|
||||
it.network == ncCb.network && it.lp.captivePortalData != null
|
||||
}.lp
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import com.android.testutils.RecorderCallback.CallbackEntry.Reserved
|
|||
import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
|
||||
import com.android.testutils.TestableNetworkCallback
|
||||
import com.android.testutils.TestableNetworkOfferCallback
|
||||
import com.android.testutils.TestableNetworkOfferCallback.CallbackEntry.OnNetworkNeeded
|
||||
import com.android.testutils.TestableNetworkOfferCallback.CallbackEntry.Needed
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import org.junit.Before
|
||||
|
|
@ -134,7 +134,7 @@ class CSNetworkReservationTest : CSTest() {
|
|||
|
||||
// validate the offer does not receive onNetworkNeeded for reservation request
|
||||
offerCb.expectNoCallbackWhere {
|
||||
it is OnNetworkNeeded && it.request.type == NetworkRequest.Type.RESERVATION
|
||||
it is Needed && it.request.type == NetworkRequest.Type.RESERVATION
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ import com.android.testutils.DevSdkIgnoreRunner
|
|||
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
|
||||
import com.android.testutils.TestableNetworkCallback
|
||||
import com.android.testutils.TestableNetworkOfferCallback
|
||||
import com.android.testutils.TestableNetworkOfferCallback.CallbackEntry.OnNetworkNeeded
|
||||
import com.android.testutils.TestableNetworkOfferCallback.CallbackEntry.OnNetworkUnneeded
|
||||
import com.android.testutils.TestableNetworkOfferCallback.CallbackEntry.Needed
|
||||
import com.android.testutils.TestableNetworkOfferCallback.CallbackEntry.Unneeded
|
||||
import com.android.testutils.postAndWait
|
||||
import com.android.testutils.runAsShell
|
||||
import org.junit.Rule
|
||||
|
|
@ -276,9 +276,9 @@ class CSPreferenceTest : CSTest() {
|
|||
fun testOptInSatellitePreference_NetworkConnectedFirst_RestrictedSatellite() =
|
||||
doTestSatellitePreference_NetworkConnectedFirst(satelliteRestricted = true)
|
||||
|
||||
private val OnNetworkNeeded.isRestricted
|
||||
private val Needed.isRestricted
|
||||
get() = !request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
|
||||
private val OnNetworkUnneeded.isRestricted
|
||||
private val Unneeded.isRestricted
|
||||
get() = !request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
|
||||
|
||||
/**
|
||||
|
|
@ -297,20 +297,14 @@ class CSPreferenceTest : CSTest() {
|
|||
val provider = NetworkProvider(context, csHandlerThread.looper, "Test provider")
|
||||
cm.registerNetworkProvider(provider)
|
||||
|
||||
val satelliteCallback = TestableNetworkOfferCallback(
|
||||
DEFAULT_TIMEOUT_MS,
|
||||
DEFAULT_NO_CALLBACK_TIMEOUT_MS
|
||||
)
|
||||
val satelliteCallback = TestableNetworkOfferCallback()
|
||||
provider.registerNetworkOffer(
|
||||
NetworkScore.Builder().build(),
|
||||
satelliteNc(restricted = false),
|
||||
Runnable::run,
|
||||
satelliteCallback
|
||||
)
|
||||
val restrictedSatelliteCallback = TestableNetworkOfferCallback(
|
||||
DEFAULT_TIMEOUT_MS,
|
||||
DEFAULT_NO_CALLBACK_TIMEOUT_MS
|
||||
)
|
||||
val restrictedSatelliteCallback = TestableNetworkOfferCallback()
|
||||
provider.registerNetworkOffer(
|
||||
NetworkScore.Builder().build(),
|
||||
satelliteNc(restricted = true),
|
||||
|
|
@ -320,18 +314,18 @@ class CSPreferenceTest : CSTest() {
|
|||
satelliteCallback.assertNoCallback()
|
||||
|
||||
updateSatellitePreference(emptySet(), setOf(UID1))
|
||||
satelliteCallback.expectCallbackThat<OnNetworkNeeded> { true }
|
||||
satelliteCallback.expect<Needed>()
|
||||
satelliteCallback.assertNoCallback()
|
||||
restrictedSatelliteCallback.assertNoCallback()
|
||||
|
||||
updateSatellitePreference(emptySet(), emptySet())
|
||||
satelliteCallback.expectCallbackThat<OnNetworkUnneeded> { true }
|
||||
satelliteCallback.expect<Unneeded>()
|
||||
satelliteCallback.assertNoCallback()
|
||||
restrictedSatelliteCallback.assertNoCallback()
|
||||
|
||||
updateSatellitePreference(setOf(SMSUID), emptySet())
|
||||
satelliteCallback.expectCallbackThat<OnNetworkNeeded> { it.isRestricted }
|
||||
restrictedSatelliteCallback.expectCallbackThat<OnNetworkNeeded> { it.isRestricted }
|
||||
satelliteCallback.expect<Needed> { it.isRestricted }
|
||||
restrictedSatelliteCallback.expect<Needed> { it.isRestricted }
|
||||
satelliteCallback.assertNoCallback()
|
||||
restrictedSatelliteCallback.assertNoCallback()
|
||||
|
||||
|
|
@ -346,39 +340,39 @@ class CSPreferenceTest : CSTest() {
|
|||
// happening because updating the preferences removes the requests, which causes a
|
||||
// rematch (where the requests are not registered), then adds the requests again, which
|
||||
// causes another rematch.
|
||||
satelliteCallback.expectCallbackThat<OnNetworkUnneeded> { it.isRestricted }
|
||||
restrictedSatelliteCallback.expectCallbackThat<OnNetworkUnneeded> { it.isRestricted }
|
||||
satelliteCallback.expectCallbackThat<OnNetworkNeeded> { !it.isRestricted }
|
||||
satelliteCallback.expectCallbackThat<OnNetworkNeeded> { it.isRestricted }
|
||||
restrictedSatelliteCallback.expectCallbackThat<OnNetworkNeeded> { it.isRestricted }
|
||||
satelliteCallback.expect<Unneeded> { it.isRestricted }
|
||||
restrictedSatelliteCallback.expect<Unneeded> { it.isRestricted }
|
||||
satelliteCallback.expect<Needed> { !it.isRestricted }
|
||||
satelliteCallback.expect<Needed> { it.isRestricted }
|
||||
restrictedSatelliteCallback.expect<Needed> { it.isRestricted }
|
||||
satelliteCallback.assertNoCallback()
|
||||
restrictedSatelliteCallback.assertNoCallback()
|
||||
|
||||
updateSatellitePreference(emptySet(), setOf(UID1))
|
||||
satelliteCallback.expectCallbackThat<OnNetworkUnneeded> { it.isRestricted }
|
||||
satelliteCallback.expectCallbackThat<OnNetworkUnneeded> { !it.isRestricted }
|
||||
satelliteCallback.expectCallbackThat<OnNetworkNeeded> { !it.isRestricted }
|
||||
restrictedSatelliteCallback.expectCallbackThat<OnNetworkUnneeded> { it.isRestricted }
|
||||
satelliteCallback.expect<Unneeded> { it.isRestricted }
|
||||
satelliteCallback.expect<Unneeded> { !it.isRestricted }
|
||||
satelliteCallback.expect<Needed> { !it.isRestricted }
|
||||
restrictedSatelliteCallback.expect<Unneeded> { it.isRestricted }
|
||||
satelliteCallback.assertNoCallback()
|
||||
restrictedSatelliteCallback.assertNoCallback()
|
||||
|
||||
updateSatellitePreference(setOf(SMSUID), setOf(UID1))
|
||||
restrictedSatelliteCallback.expectCallbackThat<OnNetworkNeeded> { it.isRestricted }
|
||||
restrictedSatelliteCallback.expect<Needed> { it.isRestricted }
|
||||
satelliteCallback.assertNoCallback()
|
||||
restrictedSatelliteCallback.assertNoCallback()
|
||||
|
||||
val wifiAgent = Agent(TRANSPORT_WIFI)
|
||||
wifiAgent.connect()
|
||||
satelliteCallback.expectCallbackThat<OnNetworkUnneeded> { it.isRestricted }
|
||||
satelliteCallback.expectCallbackThat<OnNetworkUnneeded> { !it.isRestricted }
|
||||
restrictedSatelliteCallback.expectCallbackThat<OnNetworkUnneeded> { it.isRestricted }
|
||||
satelliteCallback.expect<Unneeded> { it.isRestricted }
|
||||
satelliteCallback.expect<Unneeded> { !it.isRestricted }
|
||||
restrictedSatelliteCallback.expect<Unneeded> { it.isRestricted }
|
||||
satelliteCallback.assertNoCallback()
|
||||
restrictedSatelliteCallback.assertNoCallback()
|
||||
|
||||
wifiAgent.disconnect()
|
||||
satelliteCallback.expectCallbackThat<OnNetworkNeeded> { it.isRestricted }
|
||||
satelliteCallback.expectCallbackThat<OnNetworkNeeded> { !it.isRestricted }
|
||||
restrictedSatelliteCallback.expectCallbackThat<OnNetworkNeeded> { it.isRestricted }
|
||||
satelliteCallback.expect<Needed> { it.isRestricted }
|
||||
satelliteCallback.expect<Needed> { !it.isRestricted }
|
||||
restrictedSatelliteCallback.expect<Needed> { it.isRestricted }
|
||||
satelliteCallback.assertNoCallback()
|
||||
restrictedSatelliteCallback.assertNoCallback()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue