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 package android.extractnativelibs.cts; 17 18 import static org.junit.Assert.assertNotNull; 19 import static org.junit.Assert.assertTrue; 20 21 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.targetprep.BuildError; 24 import com.android.tradefed.targetprep.TargetSetupError; 25 import com.android.tradefed.targetprep.suite.SuiteApkInstaller; 26 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 27 import com.android.tradefed.util.AbiUtils; 28 import com.android.tradefed.util.FileUtil; 29 30 import org.junit.After; 31 import org.junit.Before; 32 33 import java.io.BufferedOutputStream; 34 import java.io.File; 35 import java.io.FileOutputStream; 36 import java.io.InputStream; 37 import java.io.OutputStream; 38 import java.util.Arrays; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.Map; 42 import java.util.Set; 43 44 /** 45 * TODO(b/147496159): add more tests. 46 */ 47 public class CtsExtractNativeLibsHostTestBase extends BaseHostJUnit4Test { 48 static final String TEST_REMOTE_DIR = "/data/local/tmp/extract_native_libs_test"; 49 static final String TEST_APK_RESOURCE_PREFIX = "/prebuilt/"; 50 static final String TEST_HOST_TMP_DIR_PREFIX = "cts_extract_native_libs_host_test"; 51 52 static final String TEST_APK_NAME_BASE = "CtsExtractNativeLibsApp"; 53 static final String TEST_PKG_NAME_BASE = "com.android.cts.extractnativelibs.app"; 54 static final String TEST_NO_EXTRACT_PKG = TEST_PKG_NAME_BASE + ".noextract"; 55 static final String TEST_NO_EXTRACT_CLASS = 56 TEST_NO_EXTRACT_PKG + ".ExtractNativeLibsFalseDeviceTest"; 57 static final String TEST_NO_EXTRACT_TEST = "testNativeLibsNotExtracted"; 58 59 static final String TEST_EXTRACT_PKG = TEST_PKG_NAME_BASE + ".extract"; 60 static final String TEST_EXTRACT_CLASS = 61 TEST_EXTRACT_PKG + ".ExtractNativeLibsTrueDeviceTest"; 62 static final String TEST_EXTRACT_TEST = "testNativeLibsExtracted"; 63 64 static final String TEST_NATIVE_LIB_LOADED_TEST = "testNativeLibsLoaded"; 65 static final String IDSIG_SUFFIX = ".idsig"; 66 67 /** Setup test dir. */ 68 @Before setUp()69 public void setUp() throws Exception { 70 getDevice().executeShellCommand("mkdir " + TEST_REMOTE_DIR); 71 } 72 73 /** Uninstall apps after tests. */ 74 @After cleanUp()75 public void cleanUp() throws Exception { 76 uninstallPackage(getDevice(), TEST_NO_EXTRACT_PKG); 77 uninstallPackage(getDevice(), TEST_EXTRACT_PKG); 78 getDevice().executeShellCommand("rm -r " + TEST_REMOTE_DIR); 79 } 80 isIncrementalInstallSupported()81 boolean isIncrementalInstallSupported() throws Exception { 82 return "true\n".equals(getDevice().executeShellCommand( 83 "pm has-feature android.software.incremental_delivery")); 84 } 85 getTestApkName(boolean isExtractNativeLibs, String abiSuffix)86 static String getTestApkName(boolean isExtractNativeLibs, String abiSuffix) { 87 return TEST_APK_NAME_BASE + (isExtractNativeLibs ? "True" : "False") + abiSuffix + ".apk"; 88 } 89 getTestPackageName(boolean isExtractNativeLibs)90 static String getTestPackageName(boolean isExtractNativeLibs) { 91 return isExtractNativeLibs ? TEST_EXTRACT_PKG : TEST_NO_EXTRACT_PKG; 92 } 93 getTestClassName(boolean isExtractNativeLibs)94 static String getTestClassName(boolean isExtractNativeLibs) { 95 return isExtractNativeLibs ? TEST_EXTRACT_CLASS : TEST_NO_EXTRACT_CLASS; 96 } 97 installPackage(boolean isIncremental, String apkName)98 final void installPackage(boolean isIncremental, String apkName) throws Exception { 99 installPackage(isIncremental, apkName, ""); 100 } 101 installPackage(boolean isIncremental, String apkName, String abi)102 final void installPackage(boolean isIncremental, String apkName, String abi) throws Exception { 103 if (isIncremental) { 104 installPackageIncremental(apkName, abi); 105 } else { 106 installPackageLegacy(apkName, abi); 107 } 108 } 109 checkNativeLibDir(boolean isExtractNativeLibs, String abi)110 final boolean checkNativeLibDir(boolean isExtractNativeLibs, String abi) throws Exception { 111 if (isExtractNativeLibs) { 112 return checkExtractedNativeLibDirForAbi(abi); 113 } else { 114 return runDeviceTests( 115 TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST); 116 } 117 } 118 getFileFromResource(String filenameInResources)119 File getFileFromResource(String filenameInResources) throws Exception { 120 String fullResourceName = TEST_APK_RESOURCE_PREFIX + filenameInResources; 121 File tempDir = FileUtil.createTempDir(TEST_HOST_TMP_DIR_PREFIX); 122 File file = new File(tempDir, filenameInResources); 123 InputStream in = getClass().getResourceAsStream(fullResourceName); 124 if (in == null) { 125 throw new IllegalArgumentException("Resource not found: " + fullResourceName); 126 } 127 OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); 128 byte[] buf = new byte[65536]; 129 int chunkSize; 130 while ((chunkSize = in.read(buf)) != -1) { 131 out.write(buf, 0, chunkSize); 132 } 133 out.close(); 134 return file; 135 } 136 runDeviceTestsWithArgs(String pkgName, String testClassName, String testMethodName, Map<String, String> testArgs)137 private boolean runDeviceTestsWithArgs(String pkgName, String testClassName, 138 String testMethodName, Map<String, String> testArgs) throws Exception { 139 final String testRunner = "androidx.test.runner.AndroidJUnitRunner"; 140 final long defaultTestTimeoutMs = 60 * 1000L; 141 final long defaultMaxTimeoutToOutputMs = 60 * 1000L; // 1min 142 return runDeviceTests(getDevice(), testRunner, pkgName, testClassName, testMethodName, 143 null, defaultTestTimeoutMs, defaultMaxTimeoutToOutputMs, 144 0L, true, false, testArgs); 145 } 146 installPackageLegacy(String apkFileName, String abi)147 private void installPackageLegacy(String apkFileName, String abi) 148 throws DeviceNotAvailableException, TargetSetupError { 149 SuiteApkInstaller installer = new SuiteApkInstaller(); 150 installer.addTestFileName(apkFileName); 151 final String abiFlag = createAbiFlag(abi); 152 if (!abiFlag.isEmpty()) { 153 installer.addInstallArg(abiFlag); 154 } 155 try { 156 installer.setUp(getTestInformation()); 157 } catch (BuildError e) { 158 throw new TargetSetupError(e.getMessage(), e, getDevice().getDeviceDescriptor()); 159 } 160 } 161 checkExtractedNativeLibDirForAbi(String abiSuffix)162 private boolean checkExtractedNativeLibDirForAbi(String abiSuffix) throws Exception { 163 final String libAbi = getExpectedLibAbi(abiSuffix); 164 assertNotNull(libAbi); 165 final String expectedSubDirArg = "expectedSubDir"; 166 final String expectedNativeLibSubDir = AbiUtils.getArchForAbi(libAbi); 167 final Map<String, String> testArgs = new HashMap<>(); 168 testArgs.put(expectedSubDirArg, expectedNativeLibSubDir); 169 return runDeviceTestsWithArgs(TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST, 170 testArgs); 171 } 172 173 /** Given the abi included in the APK, predict which abi libs will be installed 174 * @param abiSuffix "64" means the APK contains only 64-bit native libs 175 * "32" means the APK contains only 32-bit native libs 176 * "Both" means the APK contains both 32-bit and 64-bit native libs 177 * @return an ABI string from AbiUtils.ABI_* 178 * @return an ABI string from AbiUtils.ABI_* 179 */ getExpectedLibAbi(String abiSuffix)180 final String getExpectedLibAbi(String abiSuffix) { 181 final String testAbi = getAbi().getName(); 182 final String testBitness = AbiUtils.getBitness(testAbi); 183 final String libBitness; 184 // Use 32-bit native libs if test only supports 32-bit or APK only has 32-libs native libs 185 if (abiSuffix.equals("32") || testBitness.equals("32")) { 186 libBitness = "32"; 187 } else { 188 libBitness = "64"; 189 } 190 final Set<String> libAbis = AbiUtils.getAbisForArch(AbiUtils.getBaseArchForAbi(testAbi)); 191 for (String libAbi : libAbis) { 192 if (AbiUtils.getBitness(libAbi).equals(libBitness)) { 193 return libAbi; 194 } 195 } 196 return null; 197 } 198 199 /** 200 * Get ABIs supported by the test APKs. For example, if the test ABI is "armeabi-v7a", the test 201 * APKs should include native libs for "armeabi-v7a" and/or "arm64-v8a". 202 */ getApkAbis()203 final Set<String> getApkAbis() throws Exception { 204 String testApkBaseArch = AbiUtils.getArchForAbi(getAbi().getName()); 205 return AbiUtils.getAbisForArch(testApkBaseArch); 206 } 207 getDeviceAbi()208 final String getDeviceAbi() throws Exception { 209 return getDevice().getProperty("ro.product.cpu.abi"); 210 } 211 212 /** 213 * Device ABIs might includes native bridge ABIs that are different from base arch (e.g., 214 * emulators with NDK translations). 215 */ getDeviceAbis()216 final Set<String> getDeviceAbis() throws Exception { 217 String[] deviceAbis = getDevice().getProperty("ro.product.cpu.abilist").split(","); 218 return new HashSet<>(Arrays.asList(deviceAbis)); 219 } 220 getDeviceAbiSuffixes()221 final Set<String> getDeviceAbiSuffixes() throws Exception { 222 HashSet<String> abiSuffixes = new HashSet<String>(); 223 for (String abi : getDeviceAbis()) { 224 abiSuffixes.add(AbiUtils.getBitness(abi)); 225 } 226 return abiSuffixes; 227 } 228 installPackageIncremental(String apkName, String abi)229 private void installPackageIncremental(String apkName, String abi) throws Exception { 230 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); 231 final File apk = buildHelper.getTestFile(apkName); 232 assertNotNull(apk); 233 final File v4Signature = buildHelper.getTestFile(apkName + IDSIG_SUFFIX); 234 assertNotNull(v4Signature); 235 installPackageIncrementalFromFiles(apk, v4Signature, abi); 236 } 237 installPackageIncrementalFromFiles(File apk, File v4Signature, String abi)238 private String installPackageIncrementalFromFiles(File apk, File v4Signature, String abi) 239 throws Exception { 240 final String remoteApkPath = TEST_REMOTE_DIR + "/" + apk.getName(); 241 final String remoteIdsigPath = remoteApkPath + IDSIG_SUFFIX; 242 assertTrue(getDevice().pushFile(apk, remoteApkPath)); 243 assertTrue(getDevice().pushFile(v4Signature, remoteIdsigPath)); 244 return getDevice().executeShellCommand("pm install-incremental " 245 + createAbiFlag(abi) 246 + " -t -g " + remoteApkPath); 247 } 248 createAbiFlag(String abi)249 private String createAbiFlag(String abi) { 250 return abi.isEmpty() ? "" : ("--abi " + abi); 251 } 252 installIncrementalPackageFromResource(String apkFilenameInRes)253 final String installIncrementalPackageFromResource(String apkFilenameInRes) 254 throws Exception { 255 final File apkFile = getFileFromResource(apkFilenameInRes); 256 final File v4SignatureFile = getFileFromResource( 257 apkFilenameInRes + IDSIG_SUFFIX); 258 return installPackageIncrementalFromFiles(apkFile, v4SignatureFile, ""); 259 } 260 } 261