Setting autoStart to false allows tests to manually start the TapPacketReader rule, either via method call (allowing callers to specify their own Handler) or an annotation. Bug: 168868607 Test: atest NetworkStackIntegrationTests using autoStart = false Change-Id: Ic295d659250795c45181dc3c0ac0aaacba854f0b
154 lines
6.3 KiB
Kotlin
154 lines
6.3 KiB
Kotlin
/*
|
|
* Copyright (C) 2020 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.Manifest.permission.MANAGE_TEST_NETWORKS
|
|
import android.net.TestNetworkInterface
|
|
import android.net.TestNetworkManager
|
|
import android.os.Handler
|
|
import android.os.HandlerThread
|
|
import androidx.test.platform.app.InstrumentationRegistry
|
|
import org.junit.rules.TestRule
|
|
import org.junit.runner.Description
|
|
import org.junit.runners.model.Statement
|
|
import kotlin.test.assertFalse
|
|
import kotlin.test.fail
|
|
|
|
private const val HANDLER_TIMEOUT_MS = 10_000L
|
|
|
|
/**
|
|
* A [TestRule] that sets up a [TapPacketReader] on a [TestNetworkInterface] for use in the test.
|
|
*
|
|
* @param maxPacketSize Maximum size of packets read in the [TapPacketReader] buffer.
|
|
* @param autoStart Whether to initialize the interface and start the reader automatically for every
|
|
* test. If false, each test must either call start() and stop(), or be annotated
|
|
* with TapPacketReaderTest before using the reader or interface.
|
|
*/
|
|
class TapPacketReaderRule @JvmOverloads constructor(
|
|
private val maxPacketSize: Int = 1500,
|
|
private val autoStart: Boolean = true
|
|
) : TestRule {
|
|
// Use lateinit as the below members can't be initialized in the rule constructor (the
|
|
// InstrumentationRegistry may not be ready), but from the point of view of test cases using
|
|
// this rule with autoStart = true, the members are always initialized (in setup/test/teardown):
|
|
// tests cases should be able use them directly.
|
|
// lateinit also allows getting good exceptions detailing what went wrong if the members are
|
|
// referenced before they could be initialized (typically if autoStart is false and the test
|
|
// does not call start or use @TapPacketReaderTest).
|
|
lateinit var iface: TestNetworkInterface
|
|
lateinit var reader: TapPacketReader
|
|
|
|
@Volatile
|
|
private var readerRunning = false
|
|
|
|
/**
|
|
* Indicates that the [TapPacketReaderRule] should initialize its [TestNetworkInterface] and
|
|
* start the [TapPacketReader] before the test, and tear them down afterwards.
|
|
*
|
|
* For use when [TapPacketReaderRule] is created with autoStart = false.
|
|
*/
|
|
annotation class TapPacketReaderTest
|
|
|
|
/**
|
|
* Initialize the tap interface and start the [TapPacketReader].
|
|
*
|
|
* Tests using this method must also call [stop] before exiting.
|
|
* @param handler Handler to run the reader on. Callers are responsible for safely terminating
|
|
* the handler when the test ends. If null, a handler thread managed by the
|
|
* rule will be used.
|
|
*/
|
|
@JvmOverloads
|
|
fun start(handler: Handler? = null) {
|
|
if (this::iface.isInitialized) {
|
|
fail("${TapPacketReaderRule::class.java.simpleName} was already started")
|
|
}
|
|
|
|
val ctx = InstrumentationRegistry.getInstrumentation().context
|
|
iface = runAsShell(MANAGE_TEST_NETWORKS) {
|
|
val tnm = ctx.getSystemService(TestNetworkManager::class.java)
|
|
?: fail("Could not obtain the TestNetworkManager")
|
|
tnm.createTapInterface()
|
|
}
|
|
val usedHandler = handler ?: HandlerThread(
|
|
TapPacketReaderRule::class.java.simpleName).apply { start() }.threadHandler
|
|
reader = TapPacketReader(usedHandler, iface.fileDescriptor.fileDescriptor, maxPacketSize)
|
|
reader.startAsyncForTest()
|
|
readerRunning = true
|
|
}
|
|
|
|
/**
|
|
* Stop the [TapPacketReader].
|
|
*
|
|
* Tests calling [start] must call this method before exiting. If a handler was specified in
|
|
* [start], all messages on that handler must also be processed after calling this method and
|
|
* before exiting.
|
|
*
|
|
* If [start] was not called, calling this method is a no-op.
|
|
*/
|
|
fun stop() {
|
|
// The reader may not be initialized if the test case did not use the rule, even though
|
|
// other test cases in the same class may be using it (so test classes may call stop in
|
|
// tearDown even if start is not called for all test cases).
|
|
if (!this::reader.isInitialized) return
|
|
reader.handler.post {
|
|
reader.stop()
|
|
readerRunning = false
|
|
}
|
|
}
|
|
|
|
override fun apply(base: Statement, description: Description): Statement {
|
|
return TapReaderStatement(base, description)
|
|
}
|
|
|
|
private inner class TapReaderStatement(
|
|
private val base: Statement,
|
|
private val description: Description
|
|
) : Statement() {
|
|
override fun evaluate() {
|
|
val shouldStart = autoStart ||
|
|
description.getAnnotation(TapPacketReaderTest::class.java) != null
|
|
if (shouldStart) {
|
|
start()
|
|
}
|
|
|
|
try {
|
|
base.evaluate()
|
|
} finally {
|
|
if (shouldStart) {
|
|
stop()
|
|
reader.handler.looper.apply {
|
|
quitSafely()
|
|
thread.join(HANDLER_TIMEOUT_MS)
|
|
assertFalse(thread.isAlive,
|
|
"HandlerThread did not exit within $HANDLER_TIMEOUT_MS ms")
|
|
}
|
|
}
|
|
|
|
if (this@TapPacketReaderRule::iface.isInitialized) {
|
|
iface.fileDescriptor.close()
|
|
}
|
|
}
|
|
|
|
assertFalse(readerRunning,
|
|
"stop() was not called, or the provided handler did not process the stop " +
|
|
"message before the test ended. If not using autostart, make sure to call " +
|
|
"stop() after the test. If a handler is specified in start(), make sure all " +
|
|
"messages are processed after calling stop(), before quitting (for example " +
|
|
"by using HandlerThread#quitSafely and HandlerThread#join).")
|
|
}
|
|
}
|
|
} |