Files
android_packages_modules_Co…/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
Remi NGUYEN VAN 539aece60f Add autoStart parameter to TapPacketReader rule
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
2020-10-02 15:14:21 +09:00

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).")
}
}
}