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