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