Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8679e1eb authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN Committed by android-build-merger
Browse files

Merge "Add host-driven tests for the NetworkStack"

am: 9ffb1f74

Change-Id: I43af9c2f88ffcf1c0c39152da91ef4983c12a06b
parents 53f5467e 9ffb1f74
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
//
// 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"],
}
+174 −0
Original line number Diff line number Diff line
/*
 * 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()}"
}