1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.cts.install.lib.host; 18 19 import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME; 20 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import com.android.ddmlib.Log; 25 import com.android.tradefed.build.BuildInfoKey; 26 import com.android.tradefed.device.DeviceNotAvailableException; 27 import com.android.tradefed.device.ITestDevice; 28 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 29 import com.android.tradefed.util.CommandResult; 30 import com.android.tradefed.util.CommandStatus; 31 import com.android.tradefed.util.FileUtil; 32 import com.android.tradefed.util.IRunUtil; 33 import com.android.tradefed.util.RunUtil; 34 import com.android.tradefed.util.SystemUtil; 35 36 import com.google.common.base.Stopwatch; 37 38 import java.io.File; 39 import java.io.IOException; 40 import java.time.Duration; 41 import java.util.Optional; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 45 /** 46 * Utilities to facilitate installation in tests on host side. 47 */ 48 public class InstallUtilsHost { 49 private static final String TAG = InstallUtilsHost.class.getSimpleName(); 50 private static final String APEX_INFO_EXTRACT_REGEX = 51 ".*package:\\sname='(\\S+)\\'\\sversionCode='(\\d+)'\\s.*"; 52 53 private final IRunUtil mRunUtil = new RunUtil(); 54 private final BaseHostJUnit4Test mTest; 55 InstallUtilsHost(BaseHostJUnit4Test test)56 public InstallUtilsHost(BaseHostJUnit4Test test) { 57 mTest = test; 58 } 59 60 /** 61 * Return {@code true} if and only if device supports updating apex. 62 */ isApexUpdateSupported()63 public boolean isApexUpdateSupported() throws Exception { 64 return mTest.getDevice().getBooleanProperty("ro.apex.updatable", false); 65 } 66 67 /** 68 * Return {@code true} if and only if device supports file system checkpoint. 69 */ isCheckpointSupported()70 public boolean isCheckpointSupported() throws Exception { 71 CommandResult result = mTest.getDevice().executeShellV2Command("sm supports-checkpoint"); 72 assertWithMessage("Failed to check if file system checkpoint is supported : %s", 73 result.getStderr()).that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS); 74 return "true".equals(result.getStdout().trim()); 75 } 76 77 /** 78 * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e. 79 * it has a version higher than {@code 1}). 80 * 81 * <p>This is purely to optimize tests run time. Since uninstalling an apex requires a reboot, 82 * and only a small subset of tests successfully install an apex, this code avoids ~10 83 * unnecessary reboots. 84 */ uninstallShimApexIfNecessary()85 public void uninstallShimApexIfNecessary() throws Exception { 86 if (!isApexUpdateSupported()) { 87 // Device doesn't support updating apex. Nothing to uninstall. 88 return; 89 } 90 final ITestDevice.ApexInfo shimApex = getShimApex().orElseThrow( 91 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)); 92 if (shimApex.sourceDir.startsWith("/system")) { 93 // System version is active, nothing to uninstall. 94 return; 95 } 96 // Non system version is active, need to uninstall it and reboot the device. 97 Log.i(TAG, "Uninstalling shim apex"); 98 final String errorMessage = mTest.getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME); 99 if (errorMessage != null) { 100 Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage); 101 } else { 102 mTest.getDevice().reboot(); 103 final ITestDevice.ApexInfo shim = getShimApex().orElseThrow( 104 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)); 105 assertThat(shim.versionCode).isEqualTo(1L); 106 assertThat(shim.sourceDir).startsWith("/system"); 107 } 108 } 109 110 /** 111 * Returns the active shim apex as optional. 112 */ getShimApex()113 public Optional<ITestDevice.ApexInfo> getShimApex() throws DeviceNotAvailableException { 114 return mTest.getDevice().getActiveApexes().stream().filter( 115 apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny(); 116 } 117 118 /** 119 * Retrieve package name and version code from test apex file. 120 * 121 * @param apex input apex file to retrieve the info from 122 */ getApexInfo(File apex)123 public ITestDevice.ApexInfo getApexInfo(File apex) { 124 String aaptOutput = runCmd(String.format("aapt dump badging %s", apex.getAbsolutePath())); 125 String[] lines = aaptOutput.split("\n"); 126 Pattern p = Pattern.compile(APEX_INFO_EXTRACT_REGEX); 127 for (String l : lines) { 128 Matcher m = p.matcher(l); 129 if (m.matches()) { 130 return new ITestDevice.ApexInfo(m.group(1), Long.parseLong(m.group(2))); 131 } 132 } 133 return null; 134 } 135 136 /** 137 * Installs packages using staged install flow and waits for pre-reboot verification to complete 138 */ installStagedPackage(File pkg)139 public String installStagedPackage(File pkg) throws Exception { 140 return mTest.getDevice().installPackage(pkg, false, "--staged"); 141 } 142 143 /** 144 * Install multiple package at the same time 145 */ installApexes(String... filenames)146 public void installApexes(String... filenames) throws Exception { 147 String[] args = new String[filenames.length + 1]; 148 args[0] = "install-multi-package"; 149 for (int i = 0; i < filenames.length; i++) { 150 args[i + 1] = getTestFile(filenames[i]).getAbsolutePath(); 151 } 152 String stdout = mTest.getDevice().executeAdbCommand(args); 153 assertThat(stdout).isNotNull(); 154 } 155 156 /** 157 * Waits for given {@code timeout} for {@code filePath} to be deleted. 158 */ waitForFileDeleted(String filePath, Duration timeout)159 public void waitForFileDeleted(String filePath, Duration timeout) throws Exception { 160 Stopwatch stopwatch = Stopwatch.createStarted(); 161 while (true) { 162 if (!mTest.getDevice().doesFileExist(filePath)) { 163 return; 164 } 165 if (stopwatch.elapsed().compareTo(timeout) > 0) { 166 break; 167 } 168 Thread.sleep(500); 169 } 170 throw new AssertionError("Timed out waiting for " + filePath + " to be deleted"); 171 } 172 173 /** 174 * Get the test file. 175 * 176 * @param testFileName name of the file 177 */ getTestFile(String testFileName)178 public File getTestFile(String testFileName) throws IOException { 179 File testFile = null; 180 181 String testcasesPath = System.getenv( 182 SystemUtil.EnvVariable.ANDROID_HOST_OUT_TESTCASES.toString()); 183 if (testcasesPath != null) { 184 testFile = searchTestFile(new File(testcasesPath), testFileName); 185 } 186 if (testFile != null) { 187 return testFile; 188 } 189 190 File hostLinkedDir = mTest.getBuild().getFile( 191 BuildInfoKey.BuildInfoFileKey.HOST_LINKED_DIR); 192 if (hostLinkedDir != null) { 193 testFile = searchTestFile(hostLinkedDir, testFileName); 194 } 195 if (testFile != null) { 196 return testFile; 197 } 198 199 // Find the file in the buildinfo. 200 File buildInfoFile = mTest.getBuild().getFile(testFileName); 201 if (buildInfoFile != null) { 202 return buildInfoFile; 203 } 204 205 throw new IOException("Cannot find " + testFileName); 206 } 207 208 /** 209 * Searches the file with the given name under the given directory, returns null if not found. 210 */ searchTestFile(File baseSearchFile, String testFileName)211 private File searchTestFile(File baseSearchFile, String testFileName) { 212 if (baseSearchFile != null && baseSearchFile.isDirectory()) { 213 File testFile = FileUtil.findFile(baseSearchFile, testFileName); 214 if (testFile != null && testFile.isFile()) { 215 return testFile; 216 } 217 } 218 return null; 219 } 220 runCmd(String cmd)221 private String runCmd(String cmd) { 222 Log.d("About to run command: %s", cmd); 223 CommandResult result = mRunUtil.runTimedCmd(1000 * 60 * 5, cmd.split("\\s+")); 224 assertThat(result).isNotNull(); 225 assertWithMessage(String.format("Command %s failed", cmd)).that(result.getStatus()) 226 .isEqualTo(CommandStatus.SUCCESS); 227 Log.d("output:\n%s", result.getStdout()); 228 return result.getStdout(); 229 } 230 231 232 } 233