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.invoker.TestInformation; 29 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 30 import com.android.tradefed.util.CommandResult; 31 import com.android.tradefed.util.CommandStatus; 32 import com.android.tradefed.util.FileUtil; 33 import com.android.tradefed.util.IRunUtil; 34 import com.android.tradefed.util.RunUtil; 35 import com.android.tradefed.util.SystemUtil; 36 37 import com.google.common.base.Stopwatch; 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.time.Duration; 42 import java.util.List; 43 import java.util.Optional; 44 import java.util.regex.Matcher; 45 import java.util.regex.Pattern; 46 47 /** 48 * Utilities to facilitate installation in tests on host side. 49 */ 50 public class InstallUtilsHost { 51 private static final String TAG = InstallUtilsHost.class.getSimpleName(); 52 private static final String APEX_INFO_EXTRACT_REGEX = 53 ".*package:\\sname='(\\S+)\\'\\sversionCode='(\\d+)'\\s.*"; 54 55 private final IRunUtil mRunUtil = new RunUtil(); 56 private BaseHostJUnit4Test mTest = null; 57 private TestInformation mTestInfo = null; 58 InstallUtilsHost(BaseHostJUnit4Test test)59 public InstallUtilsHost(BaseHostJUnit4Test test) { 60 mTest = test; 61 } 62 InstallUtilsHost(TestInformation testInfo)63 public InstallUtilsHost(TestInformation testInfo) { 64 assertThat(testInfo).isNotNull(); 65 mTestInfo = testInfo; 66 } 67 68 /** 69 * Return {@code true} if and only if device supports updating apex. 70 */ isApexUpdateSupported()71 public boolean isApexUpdateSupported() throws Exception { 72 return getTestInfo().getDevice().getBooleanProperty("ro.apex.updatable", false); 73 } 74 75 /** 76 * Return {@code true} if and only if device supports file system checkpoint. 77 */ isCheckpointSupported()78 public boolean isCheckpointSupported() throws Exception { 79 CommandResult result = getTestInfo().getDevice().executeShellV2Command( 80 "sm supports-checkpoint"); 81 assertWithMessage("Failed to check if file system checkpoint is supported : %s", 82 result.getStderr()).that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS); 83 return "true".equals(result.getStdout().trim()); 84 } 85 86 /** 87 * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e. 88 * it has a version higher than {@code 1}). 89 * 90 * <p>This is purely to optimize tests run time. Since uninstalling an apex requires a reboot, 91 * and only a small subset of tests successfully install an apex, this code avoids ~10 92 * unnecessary reboots. 93 */ uninstallShimApexIfNecessary()94 public void uninstallShimApexIfNecessary() throws Exception { 95 final ITestDevice.ApexInfo shimApex = getShimApex().orElseThrow( 96 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)); 97 if (shimApex.sourceDir.startsWith("/system")) { 98 // System version is active, nothing to uninstall. 99 return; 100 } 101 // Non system version is active, need to uninstall it and reboot the device. 102 Log.i(TAG, "Uninstalling shim apex"); 103 final String errorMessage = 104 getTestInfo().getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME); 105 if (errorMessage != null) { 106 Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage); 107 } else { 108 getTestInfo().getDevice().reboot(); 109 final ITestDevice.ApexInfo shim = getShimApex().orElseThrow( 110 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)); 111 assertThat(shim.versionCode).isEqualTo(1L); 112 assertThat(shim.sourceDir).startsWith("/system"); 113 } 114 } 115 116 /** 117 * Returns the active shim apex as optional. 118 */ getShimApex()119 public Optional<ITestDevice.ApexInfo> getShimApex() throws DeviceNotAvailableException { 120 return getTestInfo().getDevice().getActiveApexes().stream().filter( 121 apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny(); 122 } 123 124 /** 125 * Retrieve package name and version code from test apex file. 126 * 127 * @param apex input apex file to retrieve the info from 128 */ getApexInfo(File apex)129 public ITestDevice.ApexInfo getApexInfo(File apex) { 130 String aaptOutput = runCmd(String.format("aapt dump badging %s", apex.getAbsolutePath())); 131 String[] lines = aaptOutput.split("\n"); 132 Pattern p = Pattern.compile(APEX_INFO_EXTRACT_REGEX); 133 for (String l : lines) { 134 Matcher m = p.matcher(l); 135 if (m.matches()) { 136 return new ITestDevice.ApexInfo(m.group(1), Long.parseLong(m.group(2))); 137 } 138 } 139 return null; 140 } 141 142 /** 143 * Installs packages using staged install flow and waits for pre-reboot verification to complete 144 */ installStagedPackage(File pkg)145 public String installStagedPackage(File pkg) throws Exception { 146 return getTestInfo().getDevice().installPackage(pkg, false, "--staged"); 147 } 148 149 /** 150 * Installs packages using rebootless (non-staged) install flow and waits 151 * for verification to complete 152 */ installRebootlessPackage(File pkg)153 public String installRebootlessPackage(File pkg) throws Exception { 154 return getTestInfo().getDevice().installPackage(pkg, false, "--non-staged"); 155 } 156 157 /** 158 * Install multiple package at the same time 159 */ installApexes(String... filenames)160 public void installApexes(String... filenames) throws Exception { 161 String[] args = new String[filenames.length + 1]; 162 args[0] = "install-multi-package"; 163 for (int i = 0; i < filenames.length; i++) { 164 args[i + 1] = getTestFile(filenames[i]).getAbsolutePath(); 165 } 166 String stdout = getTestInfo().getDevice().executeAdbCommand(args); 167 assertThat(stdout).isNotNull(); 168 } 169 170 /** 171 * Waits for given {@code timeout} for {@code filePath} to be deleted. 172 */ waitForFileDeleted(String filePath, Duration timeout)173 public void waitForFileDeleted(String filePath, Duration timeout) throws Exception { 174 Stopwatch stopwatch = Stopwatch.createStarted(); 175 while (true) { 176 if (!getTestInfo().getDevice().doesFileExist(filePath)) { 177 return; 178 } 179 if (stopwatch.elapsed().compareTo(timeout) > 0) { 180 break; 181 } 182 Thread.sleep(500); 183 } 184 throw new AssertionError("Timed out waiting for " + filePath + " to be deleted"); 185 } 186 187 /** 188 * Get the test file. 189 * 190 * @param testFileName name of the file 191 */ getTestFile(String testFileName)192 public File getTestFile(String testFileName) throws IOException { 193 File testFile = null; 194 195 final List<File> testCasesDirs = SystemUtil.getTestCasesDirs(getTestInfo().getBuildInfo()); 196 for (File testCasesDir : testCasesDirs) { 197 testFile = searchTestFile(testCasesDir, testFileName); 198 if (testFile != null) { 199 return testFile; 200 } 201 } 202 203 File hostLinkedDir = getTestInfo().getBuildInfo().getFile( 204 BuildInfoKey.BuildInfoFileKey.HOST_LINKED_DIR); 205 if (hostLinkedDir != null) { 206 testFile = searchTestFile(hostLinkedDir, testFileName); 207 } 208 if (testFile != null) { 209 return testFile; 210 } 211 212 // Find the file in the buildinfo. 213 File buildInfoFile = getTestInfo().getBuildInfo().getFile(testFileName); 214 if (buildInfoFile != null) { 215 return buildInfoFile; 216 } 217 218 throw new IOException("Cannot find " + testFileName); 219 } 220 221 /** 222 * Searches the file with the given name under the given directory, returns null if not found. 223 */ searchTestFile(File baseSearchFile, String testFileName)224 private File searchTestFile(File baseSearchFile, String testFileName) { 225 if (baseSearchFile != null && baseSearchFile.isDirectory()) { 226 File testFile = FileUtil.findFile(baseSearchFile, testFileName); 227 if (testFile != null && testFile.isFile()) { 228 return testFile; 229 } 230 } 231 return null; 232 } 233 runCmd(String cmd)234 private String runCmd(String cmd) { 235 Log.d("About to run command: %s", cmd); 236 CommandResult result = mRunUtil.runTimedCmd(1000 * 60 * 5, cmd.split("\\s+")); 237 assertThat(result).isNotNull(); 238 assertWithMessage(String.format("Command %s failed", cmd)).that(result.getStatus()) 239 .isEqualTo(CommandStatus.SUCCESS); 240 Log.d("output:\n%s", result.getStdout()); 241 return result.getStdout(); 242 } 243 getTestInfo()244 private TestInformation getTestInfo() { 245 if (mTestInfo == null) { 246 mTestInfo = mTest.getTestInformation(); 247 assertThat(mTestInfo).isNotNull(); 248 } 249 return mTestInfo; 250 } 251 } 252