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