diff options
-rw-r--r-- | tests/hostdriven/Android.bp | 29 | ||||
-rw-r--r-- | tests/hostdriven/host/src/com/android/networkstack/hosttests/NetworkStackHostTests.kt | 174 |
2 files changed, 203 insertions, 0 deletions
diff --git a/tests/hostdriven/Android.bp b/tests/hostdriven/Android.bp new file mode 100644 index 0000000..caa4f2c --- /dev/null +++ b/tests/hostdriven/Android.bp @@ -0,0 +1,29 @@ +// +// Copyright (C) 2019 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. +// + +java_test_host { + name: "NetworkStackHostTests", + srcs: ["host/src/**/*.kt"], + libs: [ + "junit", + "tradefed", + ], + static_libs: [ + "kotlin-test", + "module_test_util", + ], + data: [":NetworkStack"], +} diff --git a/tests/hostdriven/host/src/com/android/networkstack/hosttests/NetworkStackHostTests.kt b/tests/hostdriven/host/src/com/android/networkstack/hosttests/NetworkStackHostTests.kt new file mode 100644 index 0000000..bc1176f --- /dev/null +++ b/tests/hostdriven/host/src/com/android/networkstack/hosttests/NetworkStackHostTests.kt @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2019 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.networkstack.hosttests + +import com.android.tests.util.ModuleTestUtils +import com.android.tradefed.device.ITestDevice +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test +import com.android.tradefed.util.AaptParser +import com.android.tradefed.util.CommandResult +import com.android.tradefed.util.CommandStatus +import org.junit.After +import org.junit.Assume.assumeFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail + +private const val TEST_DELAY_MS = 200L +private const val APP_APK = "NetworkStack.apk" +private const val NETWORKSTACK_TIMEOUT_MS = 5 * 60_000 + +@RunWith(DeviceJUnit4ClassRunner::class) +class NetworkStackHostTests : BaseHostJUnit4Test() { + + private val mUtils = ModuleTestUtils(this) + private val mModuleApk = mUtils.getTestFile(APP_APK) + private val mPackageName = AaptParser.parse(mModuleApk)?.packageName + ?: throw IllegalStateException("Could not parse test package name") + private val mDevice by lazy { getDevice() } + + private var mDisableRootOnTeardown = false + + @Before + fun setUp() { + assertNotNull(mDevice) + // Get root for framework-only restart. Will fall back to device restart if not available. + mDisableRootOnTeardown = !mDevice.isAdbRoot() && mDevice.enableAdbRoot() + } + + @After + fun tearDown() { + if (mDisableRootOnTeardown) { + mDevice.disableAdbRoot() + } + } + + @Test + fun testInstallAndRollback() { + val initialUpdateTime = getLastUpdateTime() + waitForNetworkStackRegistered() + val error = mDevice.installPackage( + mModuleApk, false /* reinstall */, "--staged", "--enable-rollback") + // Test can't run on builds not (yet) supporting staged installs. Skip test on such builds. + assumeFalse(error != null && error.contains("Unknown option --staged")) + assertNull(error, "Error installing module package: $error") + try { + mUtils.waitForStagedSessionReady() + applyUpdateAndCheckNetworkStackRegistered() + assertNotEquals(initialUpdateTime, getLastUpdateTime(), "Update time did not change") + } finally { + assertCommandSucceeds("pm rollback-app $mPackageName") + mUtils.waitForStagedSessionReady() + applyUpdateAndCheckNetworkStackRegistered() + } + } + + /** + * Apply updates that are already staged for install on the device. + * + * <p>This can be done by rebooting the whole device, or just rebooting the framework if the + * device allows root access. + */ + private fun applyUpdateAndCheckNetworkStackRegistered() { + if (mDevice.isAdbRoot()) { + // Restart the framework to apply staged APK update + val oldPid = getNetworkStackPid() ?: fail("networkstack process not found") + val cmd = "am restart" + val amResult = mDevice.executeShellV2Command(cmd) + // am restart does not return a success code as the connection is lost: check stdout + assertTrue(amResult?.stdout?.trim()?.startsWith("Restart the system") ?: false, + "Could not restart the framework: " + makeErrorMessage(cmd, amResult)) + waitForNetworkStackRestart(oldPid) + } else { + // Without root access, need to reboot the whole device + mDevice.reboot() + } + waitForNetworkStackRegistered() + } + + private fun getLastUpdateTime(): String { + return waitForDumpsys(mDevice, "package $mPackageName", "lastUpdateTime=") + } + + private fun waitForNetworkStackRestart(oldPid: String, timeout: Int = NETWORKSTACK_TIMEOUT_MS) { + val start = System.currentTimeMillis() + while (System.currentTimeMillis() < start + timeout) { + val newPid = getNetworkStackPid() + if (newPid != null && newPid != oldPid) { + return + } + + Thread.sleep(TEST_DELAY_MS) + } + + fail("Timeout waiting for networkstack to restart") + } + + /** + * Returns the NetworkStack PID, or null if it is not running. + */ + private fun getNetworkStackPid(): String? { + return mDevice.getProcessPid(mPackageName) + } + + /** + * Wait for the NetworkStack to be registered in the system server as a system service. This is + * the case if dumpsys network_stack succeeds. + */ + private fun waitForNetworkStackRegistered() { + waitForDumpsys(mDevice, "network_stack version", "NetworkStackConnector") + } + + private fun assertCommandSucceeds(command: String): CommandResult { + return mDevice.executeShellV2Command(command).also { + assertEquals(CommandStatus.SUCCESS, it.getStatus(), makeErrorMessage(command, it)) + } + } +} + +/** + * Wait for a dumpsys command to return successfully with a line containing the specified prefix. + * @param dumpsys The x in the "dumpsys x" command to run + * @param expectedPrefix Prefix that one of the lines of the output must have + */ +private fun waitForDumpsys(device: ITestDevice, dumpsys: String, expectedPrefix: String): String { + val start = System.currentTimeMillis() + while (System.currentTimeMillis() < start + NETWORKSTACK_TIMEOUT_MS) { + val cmdResult = device.executeShellV2Command("dumpsys $dumpsys | grep '$expectedPrefix'") + if (cmdResult.getStatus() == CommandStatus.SUCCESS) { + cmdResult.stdout?.trim()?.let { + if (it.startsWith(expectedPrefix)) return it + } + } + + Thread.sleep(TEST_DELAY_MS) + } + + fail("Timeout waiting for dumpsys $dumpsys") +} + +private fun makeErrorMessage(command: String, result: CommandResult): String { + return "$command failed; stderr: ${result.getStderr()}; stdout: ${result.stdout}; " + + "status code: ${result.getExitCode()}" +} |