blob: 8f62bc332be84e94c9993497353aecb307129035 [file] [log] [blame]
/*
* 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.tests.util;
import static com.google.common.truth.Truth.assertThat;
import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.ApexInfo;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.SystemUtil.EnvVariable;
import com.google.common.base.Stopwatch;
import org.junit.Assert;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class ModuleTestUtils {
private static final String SHIM = "com.android.apex.cts.shim";
private static final String APEX_INFO_EXTRACT_REGEX =
".*package:\\sname='(\\S+)\\'\\sversionCode='(\\d+)'\\s.*";
private static final Duration WAIT_FOR_SESSION_READY_TTL = Duration.ofSeconds(10);
private static final Duration SLEEP_FOR = Duration.ofMillis(200);
protected final Pattern mIsSessionReadyPattern =
Pattern.compile("(isReady = true)|(isStagedSessionReady = true)");
protected final Pattern mIsSessionAppliedPattern =
Pattern.compile("(isApplied = true)|(isStagedSessionApplied = true)");
private IRunUtil mRunUtil = new RunUtil();
private BaseHostJUnit4Test mTest;
private IBuildInfo getBuild() {
return mTest.getBuild();
}
public ModuleTestUtils(BaseHostJUnit4Test test) {
mTest = test;
}
/**
* Retrieve package name and version code from test apex file.
*
* @param apex input apex file to retrieve the info from
*/
public ApexInfo getApexInfo(File apex) {
String aaptOutput = runCmd(String.format(
"aapt dump badging %s", apex.getAbsolutePath()));
String[] lines = aaptOutput.split("\n");
Pattern p = Pattern.compile(APEX_INFO_EXTRACT_REGEX);
for (String l : lines) {
Matcher m = p.matcher(l);
if (m.matches()) {
ApexInfo apexInfo = new ApexInfo(m.group(1), Long.parseLong(m.group(2)));
return apexInfo;
}
}
return null;
}
/**
* Get the test file.
*
* @param testFileName name of the file
*/
public File getTestFile(String testFileName) throws IOException {
File testFile = null;
String testcasesPath = System.getenv(EnvVariable.ANDROID_HOST_OUT_TESTCASES.toString());
if (testcasesPath != null) {
testFile = searchTestFile(new File(testcasesPath), testFileName);
}
if (testFile != null) {
return testFile;
}
File hostLinkedDir = getBuild().getFile(BuildInfoFileKey.HOST_LINKED_DIR);
if (hostLinkedDir != null) {
testFile = searchTestFile(hostLinkedDir, testFileName);
}
if (testFile != null) {
return testFile;
}
// Find the file in the buildinfo.
File buildInfoFile = getBuild().getFile(testFileName);
if (buildInfoFile != null) {
return buildInfoFile;
}
throw new IOException("Cannot find " + testFileName);
}
private String runCmd(String cmd) {
CLog.d("About to run command: %s", cmd);
CommandResult result = mRunUtil.runTimedCmd(1000 * 60 * 5, cmd.split("\\s+"));
Assert.assertNotNull(result);
Assert.assertTrue(
String.format("Command %s failed", cmd),
result.getStatus().equals(CommandStatus.SUCCESS));
CLog.v("output:\n%s", result.getStdout());
return result.getStdout();
}
/**
* Searches the file with the given name under the given directory, returns null if not found.
*/
private File searchTestFile(File baseSearchFile, String testFileName) {
if (baseSearchFile != null && baseSearchFile.isDirectory()) {
File testFile = FileUtil.findFile(baseSearchFile, testFileName);
if (testFile != null && testFile.isFile()) {
return testFile;
}
}
return null;
}
public void waitForStagedSessionReady() throws DeviceNotAvailableException {
// TODO: implement wait for session ready logic inside PackageManagerShellCommand instead.
boolean sessionReady = false;
Duration spentWaiting = Duration.ZERO;
while (spentWaiting.compareTo(WAIT_FOR_SESSION_READY_TTL) < 0) {
CommandResult res = mTest.getDevice().executeShellV2Command("pm get-stagedsessions");
Assert.assertEquals("", res.getStderr());
sessionReady = Stream.of(res.getStdout().split("\n")).anyMatch(this::isReadyNotApplied);
if (sessionReady) {
CLog.i("Done waiting after " + spentWaiting);
break;
}
try {
Thread.sleep(SLEEP_FOR.toMillis());
spentWaiting = spentWaiting.plus(SLEEP_FOR);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
Assert.assertTrue("Staged session wasn't ready in " + WAIT_FOR_SESSION_READY_TTL,
sessionReady);
}
/**
* Abandons any staged session that is marked {@code ready}
*/
public void abandonActiveStagedSession() throws DeviceNotAvailableException {
CommandResult res = mTest.getDevice().executeShellV2Command("pm list staged-sessions "
+ "--only-ready --only-parent --only-sessionid");
assertThat(res.getStderr()).isEqualTo("");
String activeSessionId = res.getStdout();
if (activeSessionId != null && !activeSessionId.equals("")) {
res = mTest.getDevice().executeShellV2Command("pm install-abandon "
+ activeSessionId);
if (!res.getStderr().equals("") || res.getStatus() != CommandStatus.SUCCESS) {
CLog.d("Failed to abandon session " + activeSessionId
+ " Error: " + res.getStderr());
}
}
}
/**
* Uninstalls a shim apex only if its latest version is installed on /data partition
*
* <p>This is purely to optimize tests run time, since uninstalling an apex requires a reboot.
*/
public void uninstallShimApexIfNecessary() throws Exception {
if (!isApexUpdateSupported()) {
return;
}
final String errorMessage = mTest.getDevice().uninstallPackage(SHIM);
if (errorMessage == null) {
CLog.i("Uninstalling shim apex");
mTest.getDevice().reboot();
} else {
// Most likely we tried to uninstall system version and failed. It should be fine to
// continue tests.
// TODO(b/140813980): use ApexInfo.sourceDir to decide whenever to issue an uninstall.
CLog.w("Failed to uninstall shim APEX: " + errorMessage);
}
assertThat(getShimApex().versionCode).isEqualTo(1L);
}
private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
return mTest.getDevice().getActiveApexes().stream().filter(
apex -> apex.name.equals(SHIM)).findAny().orElseThrow(
() -> new AssertionError("Can't find " + SHIM));
}
/**
* Return {@code true} if and only if device supports updating apex.
*/
public boolean isApexUpdateSupported() throws Exception {
return mTest.getDevice().getBooleanProperty("ro.apex.updatable", false);
}
private boolean isReadyNotApplied(String sessionInfo) {
boolean isReady = mIsSessionReadyPattern.matcher(sessionInfo).find();
boolean isApplied = mIsSessionAppliedPattern.matcher(sessionInfo).find();
return isReady && !isApplied;
}
/**
* Waits for given {@code timeout} for {@code filePath} to be deleted.
*/
public void waitForFileDeleted(String filePath, Duration timeout) throws Exception {
Stopwatch stopwatch = Stopwatch.createStarted();
while (true) {
if (!mTest.getDevice().doesFileExist(filePath)) {
return;
}
if (stopwatch.elapsed().compareTo(timeout) > 0) {
break;
}
Thread.sleep(500);
}
throw new AssertionError("Timed out waiting for " + filePath + " to be deleted");
}
}