1 /* 2 * Copyright (C) 2019 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 com.android.tests.util; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; 22 import com.android.tradefed.build.IBuildInfo; 23 import com.android.tradefed.device.DeviceNotAvailableException; 24 import com.android.tradefed.device.ITestDevice; 25 import com.android.tradefed.device.ITestDevice.ApexInfo; 26 import com.android.tradefed.log.LogUtil.CLog; 27 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 28 import com.android.tradefed.util.CommandResult; 29 import com.android.tradefed.util.CommandStatus; 30 import com.android.tradefed.util.FileUtil; 31 import com.android.tradefed.util.IRunUtil; 32 import com.android.tradefed.util.RunUtil; 33 import com.android.tradefed.util.SystemUtil.EnvVariable; 34 35 import com.google.common.base.Stopwatch; 36 37 import org.junit.Assert; 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.time.Duration; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 import java.util.stream.Stream; 45 46 public class ModuleTestUtils { 47 private static final String SHIM = "com.android.apex.cts.shim"; 48 private static final String APEX_INFO_EXTRACT_REGEX = 49 ".*package:\\sname='(\\S+)\\'\\sversionCode='(\\d+)'\\s.*"; 50 51 private static final Duration WAIT_FOR_SESSION_READY_TTL = Duration.ofSeconds(10); 52 private static final Duration SLEEP_FOR = Duration.ofMillis(200); 53 54 protected final Pattern mIsSessionReadyPattern = 55 Pattern.compile("(isReady = true)|(isStagedSessionReady = true)"); 56 protected final Pattern mIsSessionAppliedPattern = 57 Pattern.compile("(isApplied = true)|(isStagedSessionApplied = true)"); 58 59 private IRunUtil mRunUtil = new RunUtil(); 60 private BaseHostJUnit4Test mTest; 61 getBuild()62 private IBuildInfo getBuild() { 63 return mTest.getBuild(); 64 } 65 ModuleTestUtils(BaseHostJUnit4Test test)66 public ModuleTestUtils(BaseHostJUnit4Test test) { 67 mTest = test; 68 } 69 70 /** 71 * Retrieve package name and version code from test apex file. 72 * 73 * @param apex input apex file to retrieve the info from 74 */ getApexInfo(File apex)75 public ApexInfo getApexInfo(File apex) { 76 String aaptOutput = runCmd(String.format( 77 "aapt dump badging %s", apex.getAbsolutePath())); 78 String[] lines = aaptOutput.split("\n"); 79 Pattern p = Pattern.compile(APEX_INFO_EXTRACT_REGEX); 80 for (String l : lines) { 81 Matcher m = p.matcher(l); 82 if (m.matches()) { 83 ApexInfo apexInfo = new ApexInfo(m.group(1), Long.parseLong(m.group(2))); 84 return apexInfo; 85 } 86 } 87 return null; 88 } 89 90 /** 91 * Get the test file. 92 * 93 * @param testFileName name of the file 94 */ getTestFile(String testFileName)95 public File getTestFile(String testFileName) throws IOException { 96 File testFile = null; 97 98 String testcasesPath = System.getenv(EnvVariable.ANDROID_HOST_OUT_TESTCASES.toString()); 99 if (testcasesPath != null) { 100 testFile = searchTestFile(new File(testcasesPath), testFileName); 101 } 102 if (testFile != null) { 103 return testFile; 104 } 105 106 File hostLinkedDir = getBuild().getFile(BuildInfoFileKey.HOST_LINKED_DIR); 107 if (hostLinkedDir != null) { 108 testFile = searchTestFile(hostLinkedDir, testFileName); 109 } 110 if (testFile != null) { 111 return testFile; 112 } 113 114 // Find the file in the buildinfo. 115 File buildInfoFile = getBuild().getFile(testFileName); 116 if (buildInfoFile != null) { 117 return buildInfoFile; 118 } 119 120 throw new IOException("Cannot find " + testFileName); 121 } 122 runCmd(String cmd)123 private String runCmd(String cmd) { 124 CLog.d("About to run command: %s", cmd); 125 CommandResult result = mRunUtil.runTimedCmd(1000 * 60 * 5, cmd.split("\\s+")); 126 Assert.assertNotNull(result); 127 Assert.assertTrue( 128 String.format("Command %s failed", cmd), 129 result.getStatus().equals(CommandStatus.SUCCESS)); 130 CLog.v("output:\n%s", result.getStdout()); 131 return result.getStdout(); 132 } 133 134 /** 135 * Searches the file with the given name under the given directory, returns null if not found. 136 */ searchTestFile(File baseSearchFile, String testFileName)137 private File searchTestFile(File baseSearchFile, String testFileName) { 138 if (baseSearchFile != null && baseSearchFile.isDirectory()) { 139 File testFile = FileUtil.findFile(baseSearchFile, testFileName); 140 if (testFile != null && testFile.isFile()) { 141 return testFile; 142 } 143 } 144 return null; 145 } 146 waitForStagedSessionReady()147 public void waitForStagedSessionReady() throws DeviceNotAvailableException { 148 // TODO: implement wait for session ready logic inside PackageManagerShellCommand instead. 149 boolean sessionReady = false; 150 Duration spentWaiting = Duration.ZERO; 151 while (spentWaiting.compareTo(WAIT_FOR_SESSION_READY_TTL) < 0) { 152 CommandResult res = mTest.getDevice().executeShellV2Command("pm get-stagedsessions"); 153 Assert.assertEquals("", res.getStderr()); 154 sessionReady = Stream.of(res.getStdout().split("\n")).anyMatch(this::isReadyNotApplied); 155 if (sessionReady) { 156 CLog.i("Done waiting after " + spentWaiting); 157 break; 158 } 159 try { 160 Thread.sleep(SLEEP_FOR.toMillis()); 161 spentWaiting = spentWaiting.plus(SLEEP_FOR); 162 } catch (InterruptedException e) { 163 Thread.currentThread().interrupt(); 164 throw new RuntimeException(e); 165 } 166 } 167 Assert.assertTrue("Staged session wasn't ready in " + WAIT_FOR_SESSION_READY_TTL, 168 sessionReady); 169 } 170 171 /** 172 * Abandons any staged session that is marked {@code ready} 173 */ abandonActiveStagedSession()174 public void abandonActiveStagedSession() throws DeviceNotAvailableException { 175 CommandResult res = mTest.getDevice().executeShellV2Command("pm list staged-sessions " 176 + "--only-ready --only-parent --only-sessionid"); 177 assertThat(res.getStderr()).isEqualTo(""); 178 String activeSessionId = res.getStdout(); 179 if (activeSessionId != null && !activeSessionId.equals("")) { 180 res = mTest.getDevice().executeShellV2Command("pm install-abandon " 181 + activeSessionId); 182 if (!res.getStderr().equals("") || res.getStatus() != CommandStatus.SUCCESS) { 183 CLog.d("Failed to abandon session " + activeSessionId 184 + " Error: " + res.getStderr()); 185 } 186 } 187 } 188 189 /** 190 * Uninstalls a shim apex only if its latest version is installed on /data partition 191 * 192 * <p>This is purely to optimize tests run time, since uninstalling an apex requires a reboot. 193 */ uninstallShimApexIfNecessary()194 public void uninstallShimApexIfNecessary() throws Exception { 195 if (!isApexUpdateSupported()) { 196 return; 197 } 198 199 final ITestDevice.ApexInfo shim = getShimApex(); 200 if (shim.sourceDir.startsWith("/system")) { 201 CLog.i("Skipping uninstall of " + shim.sourceDir + ". Reason: pre-installed version"); 202 return; 203 } 204 CLog.i("Uninstalling shim apex"); 205 final String errorMessage = mTest.getDevice().uninstallPackage(SHIM); 206 if (errorMessage == null) { 207 mTest.getDevice().reboot(); 208 } else { 209 CLog.w("Failed to uninstall shim APEX: " + errorMessage); 210 } 211 assertThat(getShimApex().versionCode).isEqualTo(1L); 212 } 213 getShimApex()214 private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException { 215 return mTest.getDevice().getActiveApexes().stream().filter( 216 apex -> apex.name.equals(SHIM)).findAny().orElseThrow( 217 () -> new AssertionError("Can't find " + SHIM)); 218 } 219 220 /** 221 * Return {@code true} if and only if device supports updating apex. 222 */ isApexUpdateSupported()223 public boolean isApexUpdateSupported() throws Exception { 224 return mTest.getDevice().getBooleanProperty("ro.apex.updatable", false); 225 } 226 isReadyNotApplied(String sessionInfo)227 private boolean isReadyNotApplied(String sessionInfo) { 228 boolean isReady = mIsSessionReadyPattern.matcher(sessionInfo).find(); 229 boolean isApplied = mIsSessionAppliedPattern.matcher(sessionInfo).find(); 230 return isReady && !isApplied; 231 } 232 233 /** 234 * Waits for given {@code timeout} for {@code filePath} to be deleted. 235 */ waitForFileDeleted(String filePath, Duration timeout)236 public void waitForFileDeleted(String filePath, Duration timeout) throws Exception { 237 Stopwatch stopwatch = Stopwatch.createStarted(); 238 while (true) { 239 if (!mTest.getDevice().doesFileExist(filePath)) { 240 return; 241 } 242 if (stopwatch.elapsed().compareTo(timeout) > 0) { 243 break; 244 } 245 Thread.sleep(500); 246 } 247 throw new AssertionError("Timed out waiting for " + filePath + " to be deleted"); 248 } 249 } 250