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 if (!isApexUpdateSupported()) { 96 // Device doesn't support updating apex. Nothing to uninstall. 97 return; 98 } 99 final ITestDevice.ApexInfo shimApex = getShimApex().orElseThrow( 100 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)); 101 if (shimApex.sourceDir.startsWith("/system")) { 102 // System version is active, nothing to uninstall. 103 return; 104 } 105 // Non system version is active, need to uninstall it and reboot the device. 106 Log.i(TAG, "Uninstalling shim apex"); 107 final String errorMessage = 108 getTestInfo().getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME); 109 if (errorMessage != null) { 110 Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage); 111 } else { 112 getTestInfo().getDevice().reboot(); 113 final ITestDevice.ApexInfo shim = getShimApex().orElseThrow( 114 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)); 115 assertThat(shim.versionCode).isEqualTo(1L); 116 assertThat(shim.sourceDir).startsWith("/system"); 117 } 118 } 119 120 /** 121 * Returns the active shim apex as optional. 122 */ getShimApex()123 public Optional<ITestDevice.ApexInfo> getShimApex() throws DeviceNotAvailableException { 124 return getTestInfo().getDevice().getActiveApexes().stream().filter( 125 apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny(); 126 } 127 128 /** 129 * Retrieve package name and version code from test apex file. 130 * 131 * @param apex input apex file to retrieve the info from 132 */ getApexInfo(File apex)133 public ITestDevice.ApexInfo getApexInfo(File apex) { 134 String aaptOutput = runCmd(String.format("aapt dump badging %s", apex.getAbsolutePath())); 135 String[] lines = aaptOutput.split("\n"); 136 Pattern p = Pattern.compile(APEX_INFO_EXTRACT_REGEX); 137 for (String l : lines) { 138 Matcher m = p.matcher(l); 139 if (m.matches()) { 140 return new ITestDevice.ApexInfo(m.group(1), Long.parseLong(m.group(2))); 141 } 142 } 143 return null; 144 } 145 146 /** 147 * Installs packages using staged install flow and waits for pre-reboot verification to complete 148 */ installStagedPackage(File pkg)149 public String installStagedPackage(File pkg) throws Exception { 150 return getTestInfo().getDevice().installPackage(pkg, false, "--staged"); 151 } 152 153 /** 154 * Install multiple package at the same time 155 */ installApexes(String... filenames)156 public void installApexes(String... filenames) throws Exception { 157 String[] args = new String[filenames.length + 1]; 158 args[0] = "install-multi-package"; 159 for (int i = 0; i < filenames.length; i++) { 160 args[i + 1] = getTestFile(filenames[i]).getAbsolutePath(); 161 } 162 String stdout = getTestInfo().getDevice().executeAdbCommand(args); 163 assertThat(stdout).isNotNull(); 164 } 165 166 /** 167 * Waits for given {@code timeout} for {@code filePath} to be deleted. 168 */ waitForFileDeleted(String filePath, Duration timeout)169 public void waitForFileDeleted(String filePath, Duration timeout) throws Exception { 170 Stopwatch stopwatch = Stopwatch.createStarted(); 171 while (true) { 172 if (!getTestInfo().getDevice().doesFileExist(filePath)) { 173 return; 174 } 175 if (stopwatch.elapsed().compareTo(timeout) > 0) { 176 break; 177 } 178 Thread.sleep(500); 179 } 180 throw new AssertionError("Timed out waiting for " + filePath + " to be deleted"); 181 } 182 183 /** 184 * Get the test file. 185 * 186 * @param testFileName name of the file 187 */ getTestFile(String testFileName)188 public File getTestFile(String testFileName) throws IOException { 189 File testFile = null; 190 191 final List<File> testCasesDirs = SystemUtil.getTestCasesDirs(getTestInfo().getBuildInfo()); 192 for (File testCasesDir : testCasesDirs) { 193 testFile = searchTestFile(testCasesDir, testFileName); 194 if (testFile != null) { 195 return testFile; 196 } 197 } 198 199 File hostLinkedDir = getTestInfo().getBuildInfo().getFile( 200 BuildInfoKey.BuildInfoFileKey.HOST_LINKED_DIR); 201 if (hostLinkedDir != null) { 202 testFile = searchTestFile(hostLinkedDir, testFileName); 203 } 204 if (testFile != null) { 205 return testFile; 206 } 207 208 // Find the file in the buildinfo. 209 File buildInfoFile = getTestInfo().getBuildInfo().getFile(testFileName); 210 if (buildInfoFile != null) { 211 return buildInfoFile; 212 } 213 214 throw new IOException("Cannot find " + testFileName); 215 } 216 217 /** 218 * Searches the file with the given name under the given directory, returns null if not found. 219 */ searchTestFile(File baseSearchFile, String testFileName)220 private File searchTestFile(File baseSearchFile, String testFileName) { 221 if (baseSearchFile != null && baseSearchFile.isDirectory()) { 222 File testFile = FileUtil.findFile(baseSearchFile, testFileName); 223 if (testFile != null && testFile.isFile()) { 224 return testFile; 225 } 226 } 227 return null; 228 } 229 runCmd(String cmd)230 private String runCmd(String cmd) { 231 Log.d("About to run command: %s", cmd); 232 CommandResult result = mRunUtil.runTimedCmd(1000 * 60 * 5, cmd.split("\\s+")); 233 assertThat(result).isNotNull(); 234 assertWithMessage(String.format("Command %s failed", cmd)).that(result.getStatus()) 235 .isEqualTo(CommandStatus.SUCCESS); 236 Log.d("output:\n%s", result.getStdout()); 237 return result.getStdout(); 238 } 239 getTestInfo()240 private TestInformation getTestInfo() { 241 if (mTestInfo == null) { 242 mTestInfo = mTest.getTestInformation(); 243 assertThat(mTestInfo).isNotNull(); 244 } 245 return mTestInfo; 246 } 247 } 248