1 /*
2  * Copyright (C) 2023 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.compilation.cts;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import com.android.tradefed.invoker.TestInformation;
23 import com.android.tradefed.testtype.IAbi;
24 import com.android.tradefed.util.CommandResult;
25 import com.android.tradefed.util.Pair;
26 import com.android.tradefed.util.RunUtil;
27 
28 import com.google.common.io.ByteStreams;
29 
30 import java.io.File;
31 import java.io.FileOutputStream;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.time.Duration;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.regex.Pattern;
38 
39 public class Utils {
40     private static final Duration SOFT_REBOOT_TIMEOUT = Duration.ofMinutes(3);
41 
42     private final TestInformation mTestInfo;
43 
Utils(TestInformation testInfo)44     public Utils(TestInformation testInfo) throws Exception {
45         assertThat(testInfo.getDevice()).isNotNull();
46         mTestInfo = testInfo;
47     }
48 
assertCommandSucceeds(String... command)49     public String assertCommandSucceeds(String... command) throws Exception {
50         CommandResult result =
51                 mTestInfo.getDevice().executeShellV2Command(String.join(" ", command));
52         assertWithMessage(result.toString()).that(result.getExitCode()).isEqualTo(0);
53         // Remove trailing \n's.
54         return result.getStdout().trim();
55     }
56 
57     /**
58      * Implementation details.
59      *
60      * @param packages A list of packages, where each entry is a list of APK-DM pairs.
61      * @param multiPackage True for {@code install-multi-package}, false for {@code
62      *         install-multiple}.
63      */
installFromResourcesImpl(IAbi abi, List<String> args, List<List<Pair<String, String>>> packages, boolean multiPackage)64     private void installFromResourcesImpl(IAbi abi, List<String> args,
65             List<List<Pair<String, String>>> packages, boolean multiPackage) throws Exception {
66         // We cannot use `ITestDevice.installPackage` or `SuiteApkInstaller` here because they don't
67         // support DM files.
68         List<String> cmd =
69                 new ArrayList<>(List.of("adb", "-s", mTestInfo.getDevice().getSerialNumber(),
70                         multiPackage ? "install-multi-package" : "install-multiple", "--abi",
71                         abi.getName()));
72 
73         cmd.addAll(args);
74 
75         if (!multiPackage && packages.size() != 1) {
76             throw new IllegalArgumentException(
77                     "'install-multiple' only supports exactly one package");
78         }
79 
80         for (List<Pair<String, String>> apkDmResources : packages) {
81             List<String> files = new ArrayList<>();
82             for (Pair<String, String> pair : apkDmResources) {
83                 String apkResource = pair.first;
84                 File apkFile = copyResourceToFile(apkResource, File.createTempFile("temp", ".apk"));
85                 apkFile.deleteOnExit();
86 
87                 String dmResource = pair.second;
88                 if (dmResource != null) {
89                     File dmFile = copyResourceToFile(
90                             dmResource, new File(getDmPath(apkFile.getAbsolutePath())));
91                     dmFile.deleteOnExit();
92                     files.add(dmFile.getAbsolutePath());
93                 }
94 
95                 // To make `install-multi-package` happy, the last file must end with ".apk".
96                 files.add(apkFile.getAbsolutePath());
97             }
98 
99             if (multiPackage) {
100                 // The format is 'pkg1-base.dm:pkg1-base.apk:pkg1-split1.dm:pkg1-split1.apk
101                 // pkg2-base.dm:pkg2-base.apk:pkg2-split1.dm:pkg2-split1.apk'.
102                 cmd.add(String.join(":", files));
103             } else {
104                 // The format is 'pkg1-base.dm pkg1-base.apk pkg1-split1.dm pkg1-split1.apk'.
105                 cmd.addAll(files);
106             }
107         }
108 
109         // We can't use `INativeDevice.executeAdbCommand`. It only returns stdout on success and
110         // returns null on failure, while we want to get the exact error message.
111         CommandResult result = RunUtil.getDefault().runTimedCmd(
112                 mTestInfo.getDevice().getOptions().getAdbCommandTimeout(),
113                 cmd.toArray(String[] ::new));
114         assertWithMessage(result.toString()).that(result.getExitCode()).isEqualTo(0);
115     }
116 
117     /**
118      * Installs a package from resources with arguments.
119      *
120      * @param apkDmResources For each pair, the first item is the APK resource name, and the second
121      *         item is the DM resource name or null.
122      */
installFromResourcesWithArgs(IAbi abi, List<String> args, List<Pair<String, String>> apkDmResources)123     public void installFromResourcesWithArgs(IAbi abi, List<String> args,
124             List<Pair<String, String>> apkDmResources) throws Exception {
125         installFromResourcesImpl(abi, args, List.of(apkDmResources), false /* multiPackage */);
126     }
127 
128     /** Same as above, but takes no argument. */
installFromResources(IAbi abi, List<Pair<String, String>> apkDmResources)129     public void installFromResources(IAbi abi, List<Pair<String, String>> apkDmResources)
130             throws Exception {
131         installFromResourcesWithArgs(abi, List.of() /* args */, apkDmResources);
132     }
133 
installFromResources(IAbi abi, String apkResource, String dmResource)134     public void installFromResources(IAbi abi, String apkResource, String dmResource)
135             throws Exception {
136         installFromResources(abi, List.of(Pair.create(apkResource, dmResource)));
137     }
138 
installFromResources(IAbi abi, String apkResource)139     public void installFromResources(IAbi abi, String apkResource) throws Exception {
140         installFromResources(abi, apkResource, null);
141     }
142 
installFromResourcesMultiPackage( IAbi abi, List<List<Pair<String, String>>> packages)143     public void installFromResourcesMultiPackage(
144             IAbi abi, List<List<Pair<String, String>>> packages) throws Exception {
145         installFromResourcesImpl(abi, List.of() /* args */, packages, true /* multiPackage */);
146     }
147 
pushFromResource(String resource, String remotePath)148     public void pushFromResource(String resource, String remotePath) throws Exception {
149         File tempFile = copyResourceToFile(resource, File.createTempFile("temp", ".tmp"));
150         tempFile.deleteOnExit();
151         mTestInfo.getDevice().pushFile(tempFile, remotePath);
152     }
153 
copyResourceToFile(String resourceName, File file)154     public File copyResourceToFile(String resourceName, File file) throws Exception {
155         try (OutputStream outputStream = new FileOutputStream(file);
156                 InputStream inputStream = getClass().getResourceAsStream(resourceName)) {
157             assertThat(ByteStreams.copy(inputStream, outputStream)).isGreaterThan(0);
158         }
159         return file;
160     }
161 
softReboot()162     public void softReboot() throws Exception {
163         // `waitForBootComplete` relies on `dev.bootcomplete`.
164         mTestInfo.getDevice().executeShellCommand("setprop dev.bootcomplete 0");
165         mTestInfo.getDevice().executeShellCommand("setprop ctl.restart zygote");
166         boolean success = mTestInfo.getDevice().waitForBootComplete(SOFT_REBOOT_TIMEOUT.toMillis());
167         assertWithMessage("Soft reboot didn't complete in %ss", SOFT_REBOOT_TIMEOUT.getSeconds())
168                 .that(success)
169                 .isTrue();
170     }
171 
dumpContainsDexFile(String dump, String dexFile)172     public static void dumpContainsDexFile(String dump, String dexFile) {
173         assertThat(dump).containsMatch(dexFileToPattern(dexFile));
174     }
175 
dumpDoesNotContainDexFile(String dump, String dexFile)176     public static void dumpDoesNotContainDexFile(String dump, String dexFile) {
177         assertThat(dump).doesNotContainMatch(dexFileToPattern(dexFile));
178     }
179 
countSubstringOccurrence(String str, String subStr)180     public static int countSubstringOccurrence(String str, String subStr) {
181         return str.split(subStr, -1 /* limit */).length - 1;
182     }
183 
getDmPath(String apkPath)184     private String getDmPath(String apkPath) throws Exception {
185         return apkPath.replaceAll("\\.apk$", ".dm");
186     }
187 
dexFileToPattern(String dexFile)188     private static Pattern dexFileToPattern(String dexFile) {
189         return Pattern.compile(String.format("[\\s/](%s)\\s?", Pattern.quote(dexFile)));
190     }
191 }
192