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