• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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