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