summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRemi NGUYEN VAN <reminv@google.com>2021-11-05 04:44:35 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-11-05 04:44:35 +0000
commit37e0cdf61042a0599eb8840bcac75b0375534dd9 (patch)
treeca61adfcb81e2fe22f288c7cdfc0b0ce47fbf400
parent610f38422d60a57d7a7af895036ab1db63ca536b (diff)
parent9e57180bd695a5577e1d90a84a9f849d5b6d4128 (diff)
downloadnet-37e0cdf61042a0599eb8840bcac75b0375534dd9.tar.gz
Merge "Add ConnectivityCheckTargetPreparer" am: 9e57180bd6
Original change: https://android-review.googlesource.com/c/platform/frameworks/libs/net/+/1842719 Change-Id: I19ee013dcb06f2d1b65342638ef5b74d5f2cb08e
-rw-r--r--common/testutils/Android.bp11
-rw-r--r--common/testutils/app/connectivitychecker/Android.bp29
-rw-r--r--common/testutils/app/connectivitychecker/AndroidManifest.xml32
-rw-r--r--common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitychecker/ConnectivityCheckTest.kt65
-rw-r--r--common/testutils/devicetests/com/android/testutils/ConnectUtil.kt203
-rw-r--r--common/testutils/host/com/android/testutils/ConnectivityCheckTargetPreparer.kt77
6 files changed, 417 insertions, 0 deletions
diff --git a/common/testutils/Android.bp b/common/testutils/Android.bp
index b7297bb8..9fd30f70 100644
--- a/common/testutils/Android.bp
+++ b/common/testutils/Android.bp
@@ -61,3 +61,14 @@ java_library {
"kotlin-test"
]
}
+
+java_test_host {
+ name: "net-tests-utils-host-common",
+ srcs: [
+ "host/**/*.java",
+ "host/**/*.kt",
+ ],
+ libs: ["tradefed"],
+ test_suites: ["device-tests", "general-tests", "cts", "mts"],
+ data: [":ConnectivityChecker"],
+}
diff --git a/common/testutils/app/connectivitychecker/Android.bp b/common/testutils/app/connectivitychecker/Android.bp
new file mode 100644
index 00000000..55b585ae
--- /dev/null
+++ b/common/testutils/app/connectivitychecker/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 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.
+
+android_test_helper_app {
+ name: "ConnectivityChecker",
+ srcs: ["src/**/*.kt"],
+ sdk_version: "system_current",
+ // Allow running the test on any device with SDK Q+, even when built from a branch that uses
+ // an unstable SDK, by targeting a stable SDK regardless of the build SDK.
+ min_sdk_version: "29",
+ target_sdk_version: "30",
+ static_libs: [
+ "androidx.test.rules",
+ "modules-utils-build_system",
+ "net-tests-utils",
+ ],
+ host_required: ["net-tests-utils-host-common"],
+} \ No newline at end of file
diff --git a/common/testutils/app/connectivitychecker/AndroidManifest.xml b/common/testutils/app/connectivitychecker/AndroidManifest.xml
new file mode 100644
index 00000000..8e5958c3
--- /dev/null
+++ b/common/testutils/app/connectivitychecker/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.testutils.connectivitychecker">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <!-- For wifi scans -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.testutils.connectivitychecker"
+ android:label="Connectivity checker target preparer" />
+</manifest>
diff --git a/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitychecker/ConnectivityCheckTest.kt b/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitychecker/ConnectivityCheckTest.kt
new file mode 100644
index 00000000..43b130b2
--- /dev/null
+++ b/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitychecker/ConnectivityCheckTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.connectivitychecker
+
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.ConnectUtil
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+@RunWith(AndroidJUnit4::class)
+class ConnectivityCheckTest {
+ val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+ val pm by lazy { context.packageManager }
+
+ @Test
+ fun testCheckDeviceSetup() {
+ checkWifiSetup()
+ checkTelephonySetup()
+ }
+
+ private fun checkWifiSetup() {
+ if (!pm.hasSystemFeature(FEATURE_WIFI)) return
+ ConnectUtil(context).ensureWifiConnected()
+ }
+
+ private fun checkTelephonySetup() {
+ if (!pm.hasSystemFeature(FEATURE_TELEPHONY)) return
+ val tm = context.getSystemService(TelephonyManager::class.java)
+ ?: fail("Could not get telephony service")
+
+ val commonError = "Check the test bench. To run the tests anyway for quick & dirty local " +
+ "testing, you can use atest X -- " +
+ "--test-arg com.android.testutils.ConnectivityCheckTargetPreparer:disable:true"
+ // Do not use assertEquals: it outputs "expected X, was Y", which looks like a test failure
+ if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) {
+ fail("The device has no SIM card inserted. " + commonError)
+ } else if (tm.simState != TelephonyManager.SIM_STATE_READY) {
+ fail("The device is not setup with a usable SIM card. Sim state was ${tm.simState}. " +
+ commonError)
+ }
+ assertTrue(tm.isDataConnectivityPossible,
+ "The device is not setup with a SIM card that supports data connectivity. " +
+ commonError)
+ }
+} \ No newline at end of file
diff --git a/common/testutils/devicetests/com/android/testutils/ConnectUtil.kt b/common/testutils/devicetests/com/android/testutils/ConnectUtil.kt
new file mode 100644
index 00000000..fc951d86
--- /dev/null
+++ b/common/testutils/devicetests/com/android/testutils/ConnectUtil.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2021 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
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.wifi.ScanResult
+import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiManager
+import android.os.ParcelFileDescriptor
+import android.os.SystemClock
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val MAX_WIFI_CONNECT_RETRIES = 10
+private const val WIFI_CONNECT_INTERVAL_MS = 500L
+private const val WIFI_CONNECT_TIMEOUT_MS = 30_000L
+
+// Constants used by WifiManager.ActionListener#onFailure. Although onFailure is SystemApi,
+// the error code constants are not (b/204277752)
+private const val WIFI_ERROR_IN_PROGRESS = 1
+private const val WIFI_ERROR_BUSY = 2
+
+class ConnectUtil(private val context: Context) {
+ private val TAG = ConnectUtil::class.java.simpleName
+
+ private val cm = context.getSystemService(ConnectivityManager::class.java)
+ ?: fail("Could not find ConnectivityManager")
+ private val wifiManager = context.getSystemService(WifiManager::class.java)
+ ?: fail("Could not find WifiManager")
+
+ fun ensureWifiConnected(): Network {
+ val callback = TestableNetworkCallback()
+ cm.registerNetworkCallback(NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build(), callback)
+
+ try {
+ val connInfo = wifiManager.connectionInfo
+ if (connInfo == null || connInfo.networkId == -1) {
+ clearWifiBlocklist()
+ val pfd = getInstrumentation().uiAutomation.executeShellCommand("svc wifi enable")
+ // Read the output stream to ensure the command has completed
+ ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.readBytes() }
+ val config = getOrCreateWifiConfiguration()
+ connectToWifiConfig(config)
+ }
+ val cb = callback.eventuallyExpectOrNull<RecorderCallback.CallbackEntry.Available>(
+ timeoutMs = WIFI_CONNECT_TIMEOUT_MS)
+
+ assertNotNull(cb, "Could not connect to a wifi access point within " +
+ "$WIFI_CONNECT_INTERVAL_MS ms. Check that the test device has a wifi network " +
+ "configured, and that the test access point is functioning properly.")
+ return cb.network
+ } finally {
+ cm.unregisterNetworkCallback(callback)
+ }
+ }
+
+ private fun connectToWifiConfig(config: WifiConfiguration) {
+ repeat(MAX_WIFI_CONNECT_RETRIES) {
+ val error = runAsShell(permission.NETWORK_SETTINGS) {
+ val listener = ConnectWifiListener()
+ wifiManager.connect(config, listener)
+ listener.connectFuture.get(WIFI_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ } ?: return // Connect succeeded
+
+ // Only retry for IN_PROGRESS and BUSY
+ if (error != WIFI_ERROR_IN_PROGRESS && error != WIFI_ERROR_BUSY) {
+ fail("Failed to connect to " + config.SSID + ": " + error)
+ }
+ Log.w(TAG, "connect failed with $error; waiting before retry")
+ SystemClock.sleep(WIFI_CONNECT_INTERVAL_MS)
+ }
+ fail("Failed to connect to ${config.SSID} after $MAX_WIFI_CONNECT_RETRIES retries")
+ }
+
+ private class ConnectWifiListener : WifiManager.ActionListener {
+ /**
+ * Future completed when the connect process ends. Provides the error code or null if none.
+ */
+ val connectFuture = CompletableFuture<Int?>()
+ override fun onSuccess() {
+ connectFuture.complete(null)
+ }
+
+ override fun onFailure(reason: Int) {
+ connectFuture.complete(reason)
+ }
+ }
+
+ private fun getOrCreateWifiConfiguration(): WifiConfiguration {
+ val configs = runAsShell(permission.NETWORK_SETTINGS) {
+ wifiManager.getConfiguredNetworks()
+ }
+ // If no network is configured, add a config for virtual access points if applicable
+ if (configs.size == 0) {
+ val scanResults = getWifiScanResults()
+ val virtualConfig = maybeConfigureVirtualNetwork(scanResults)
+ assertNotNull(virtualConfig, "The device has no configured wifi network")
+ return virtualConfig
+ }
+ // No need to add a configuration: there is already one.
+ if (configs.size > 1) {
+ // For convenience in case of local testing on devices with multiple saved configs,
+ // prefer the first configuration that is in range.
+ // In actual tests, there should only be one configuration, and it should be usable as
+ // assumed by WifiManagerTest.testConnect.
+ Log.w(TAG, "Multiple wifi configurations found: " +
+ configs.joinToString(", ") { it.SSID })
+ val scanResultsList = getWifiScanResults()
+ Log.i(TAG, "Scan results: " + scanResultsList.joinToString(", ") {
+ "${it.SSID} (${it.level})"
+ })
+
+ val scanResults = scanResultsList.map { "\"${it.SSID}\"" }.toSet()
+ return configs.firstOrNull { scanResults.contains(it.SSID) } ?: configs[0]
+ }
+ return configs[0]
+ }
+
+ private fun getWifiScanResults(): List<ScanResult> {
+ val scanResultsFuture = CompletableFuture<List<ScanResult>>()
+ runAsShell(permission.NETWORK_SETTINGS) {
+ val receiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ scanResultsFuture.complete(wifiManager.scanResults)
+ }
+ }
+ context.registerReceiver(receiver,
+ IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
+ wifiManager.startScan()
+ }
+ return try {
+ scanResultsFuture.get(WIFI_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ } catch (e: Exception) {
+ throw AssertionError("Wifi scan results not received within timeout", e)
+ }
+ }
+
+ /**
+ * If a virtual wifi network is detected, add a configuration for that network.
+ * TODO(b/158150376): have the test infrastructure add virtual wifi networks when appropriate.
+ */
+ private fun maybeConfigureVirtualNetwork(scanResults: List<ScanResult>): WifiConfiguration? {
+ // Virtual wifi networks used on the emulator and cloud testing infrastructure
+ val virtualSsids = listOf("VirtWifi", "AndroidWifi")
+ Log.d(TAG, "Wifi scan results: $scanResults")
+ val virtualScanResult = scanResults.firstOrNull { virtualSsids.contains(it.SSID) }
+ ?: return null
+
+ // Only add the virtual configuration if the virtual AP is detected in scans
+ val virtualConfig = WifiConfiguration()
+ // ASCII SSIDs need to be surrounded by double quotes
+ virtualConfig.SSID = "\"${virtualScanResult.SSID}\""
+ virtualConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
+ runAsShell(permission.NETWORK_SETTINGS) {
+ val networkId = wifiManager.addNetwork(virtualConfig)
+ assertTrue(networkId >= 0)
+ assertTrue(wifiManager.enableNetwork(networkId, false /* attemptConnect */))
+ }
+ return virtualConfig
+ }
+
+ /**
+ * Re-enable wifi networks that were blocked, typically because no internet connection was
+ * detected the last time they were connected. This is necessary to make sure wifi can reconnect
+ * to them.
+ */
+ private fun clearWifiBlocklist() {
+ runAsShell(permission.NETWORK_SETTINGS, permission.ACCESS_WIFI_STATE) {
+ for (cfg in wifiManager.configuredNetworks) {
+ assertTrue(wifiManager.enableNetwork(cfg.networkId, false /* attemptConnect */))
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/common/testutils/host/com/android/testutils/ConnectivityCheckTargetPreparer.kt b/common/testutils/host/com/android/testutils/ConnectivityCheckTargetPreparer.kt
new file mode 100644
index 00000000..85589ad9
--- /dev/null
+++ b/common/testutils/host/com/android/testutils/ConnectivityCheckTargetPreparer.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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 com.android.ddmlib.testrunner.TestResult
+import com.android.tradefed.invoker.TestInformation
+import com.android.tradefed.result.CollectingTestListener
+import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner
+import com.android.tradefed.targetprep.BaseTargetPreparer
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller
+
+private const val CONNECTIVITY_CHECKER_APK = "ConnectivityChecker.apk"
+private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitychecker"
+// As per the <instrumentation> defined in the checker manifest
+private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
+
+/**
+ * A target preparer that verifies that the device was setup correctly for connectivity tests.
+ *
+ * For quick and dirty local testing, can be disabled by running tests with
+ * "atest -- --test-arg com.android.testutils.ConnectivityCheckTargetPreparer:disable:true".
+ */
+class ConnectivityCheckTargetPreparer : BaseTargetPreparer() {
+ val installer = SuiteApkInstaller()
+
+ override fun setUp(testInformation: TestInformation) {
+ if (isDisabled) return
+ installer.setCleanApk(true)
+ installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
+ installer.setShouldGrantPermission(true)
+ installer.setUp(testInformation)
+
+ val runner = DefaultRemoteAndroidTestRunner(
+ CONNECTIVITY_PKG_NAME,
+ CONNECTIVITY_CHECK_RUNNER_NAME,
+ testInformation.device.iDevice)
+ runner.runOptions = "--no-hidden-api-checks"
+
+ val receiver = CollectingTestListener()
+ if (!testInformation.device.runInstrumentationTests(runner, receiver)) {
+ throw AssertionError("Device state check failed to complete")
+ }
+
+ val runResult = receiver.currentRunResults
+ if (runResult.isRunFailure) {
+ throw AssertionError("Failed to check device state before the test: " +
+ runResult.runFailureMessage)
+ }
+
+ if (!runResult.hasFailedTests()) return
+ val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
+ if (TestResult.TestStatus.FAILURE != testResult.status) null
+ else "$testDescription: ${testResult.stackTrace}"
+ }.joinToString("\n")
+
+ throw AssertionError("Device setup checks failed. Check the test bench: \n$errorMsg")
+ }
+
+ override fun tearDown(testInformation: TestInformation?, e: Throwable?) {
+ if (isTearDownDisabled) return
+ installer.tearDown(testInformation, e)
+ }
+} \ No newline at end of file