1 /* 2 * Copyright (C) 2021 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.compat.testing; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 23 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 24 import com.android.ddmlib.testrunner.TestResult.TestStatus; 25 import com.android.tradefed.build.IBuildInfo; 26 import com.android.tradefed.device.DeviceNotAvailableException; 27 import com.android.tradefed.device.INativeDevice; 28 import com.android.tradefed.device.ITestDevice; 29 import com.android.tradefed.result.CollectingTestListener; 30 import com.android.tradefed.result.TestDescription; 31 import com.android.tradefed.result.TestResult; 32 import com.android.tradefed.result.TestRunResult; 33 import com.android.tradefed.util.CommandResult; 34 import com.android.tradefed.util.CommandStatus; 35 import com.android.tradefed.util.FileUtil; 36 37 import com.google.common.collect.ImmutableList; 38 import com.google.common.collect.ImmutableSet; 39 40 import com.android.tools.smali.dexlib2.DexFileFactory; 41 import com.android.tools.smali.dexlib2.Opcodes; 42 import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile; 43 import com.android.tools.smali.dexlib2.iface.ClassDef; 44 import com.android.tools.smali.dexlib2.iface.MultiDexContainer; 45 46 import java.io.File; 47 import java.io.FileNotFoundException; 48 import java.io.IOException; 49 import java.util.Map; 50 import java.util.Objects; 51 52 /** 53 * Testing utilities for parsing *CLASSPATH environ variables and shared libs on a test device. 54 */ 55 public final class Classpaths { 56 Classpaths()57 private Classpaths() { 58 } 59 60 public enum ClasspathType { 61 BOOTCLASSPATH, 62 DEX2OATBOOTCLASSPATH, 63 SYSTEMSERVERCLASSPATH, 64 STANDALONE_SYSTEMSERVER_JARS, 65 } 66 67 private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner"; 68 69 /** Returns on device filepaths to the jars that are part of a given classpath. */ getJarsOnClasspath(INativeDevice device, ClasspathType classpath)70 public static ImmutableList<String> getJarsOnClasspath(INativeDevice device, 71 ClasspathType classpath) throws DeviceNotAvailableException { 72 CommandResult shellResult = device.executeShellV2Command("echo $" + classpath); 73 assertThat(shellResult.getStatus()).isEqualTo(CommandStatus.SUCCESS); 74 assertThat(shellResult.getExitCode()).isEqualTo(0); 75 76 String value = shellResult.getStdout().trim(); 77 assertThat(value).isNotEmpty(); 78 return ImmutableList.copyOf(value.split(":")); 79 } 80 81 /** Returns {@link SharedLibraryInfo} about the shared libs available on the test device. */ getSharedLibraryInfos(ITestDevice device, IBuildInfo buildInfo)82 public static ImmutableList<SharedLibraryInfo> getSharedLibraryInfos(ITestDevice device, 83 IBuildInfo buildInfo) throws DeviceNotAvailableException, FileNotFoundException { 84 runDeviceTests(device, buildInfo, SharedLibraryInfo.HELPER_APP_APK, 85 SharedLibraryInfo.HELPER_APP_PACKAGE, SharedLibraryInfo.HELPER_APP_CLASS); 86 String remoteFile = "/sdcard/shared-libs.txt"; 87 String content; 88 try { 89 content = device.pullFileContents(remoteFile); 90 } finally { 91 device.deleteFile(remoteFile); 92 } 93 return SharedLibraryInfo.getSharedLibraryInfos(content); 94 } 95 96 /** Returns classes defined a given jar file on the test device. */ getClassDefsFromJar(File jar)97 public static ImmutableSet<ClassDef> getClassDefsFromJar(File jar) throws IOException { 98 MultiDexContainer<? extends DexBackedDexFile> container = 99 DexFileFactory.loadDexContainer(jar, Opcodes.getDefault()); 100 ImmutableSet.Builder<ClassDef> set = ImmutableSet.builder(); 101 for (String dexName : container.getDexEntryNames()) { 102 DexBackedDexFile dexFile = container.getEntry(dexName).getDexFile(); 103 set.addAll(Objects.requireNonNull(dexFile).getClasses()); 104 } 105 return set.build(); 106 } 107 runDeviceTests(ITestDevice device, IBuildInfo buildInfo, String apkName, String packageName, String className)108 private static void runDeviceTests(ITestDevice device, IBuildInfo buildInfo, String apkName, 109 String packageName, String className) throws DeviceNotAvailableException, 110 FileNotFoundException { 111 try { 112 final CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo); 113 final String installError = device.installPackage(buildHelper.getTestFile(apkName), 114 false); 115 assertWithMessage("Failed to install %s due to: %s", apkName, installError). 116 that(installError).isNull(); 117 // Trigger helper app to collect and write info about shared libraries on the device. 118 final RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName, 119 TEST_RUNNER, device.getIDevice()); 120 testRunner.setClassName(className); 121 final CollectingTestListener listener = new CollectingTestListener(); 122 assertThat(device.runInstrumentationTests(testRunner, listener)).isTrue(); 123 final TestRunResult result = listener.getCurrentRunResults(); 124 assertWithMessage("Failed to successfully run device tests for " + result.getName() 125 + ": " + result.getRunFailureMessage()) 126 .that(result.isRunFailure()).isFalse(); 127 assertWithMessage("No tests were run!").that(result.getNumTests()).isGreaterThan(0); 128 StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n"); 129 for (Map.Entry<TestDescription, TestResult> resultEntry : 130 result.getTestResults().entrySet()) { 131 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) { 132 errorBuilder.append(resultEntry.getKey().toString()); 133 errorBuilder.append(":\n"); 134 errorBuilder.append(resultEntry.getValue().getStackTrace()); 135 } 136 } 137 assertWithMessage(errorBuilder.toString()).that(result.hasFailedTests()).isFalse(); 138 } finally { 139 device.uninstallPackage(packageName); 140 } 141 } 142 143 } 144