1 /*
2  * Copyright (C) 2017 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 com.android.compatibility.common.tradefed.presubmit;
17 
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertTrue;
20 import static org.junit.Assert.fail;
21 
22 import com.android.tradefed.testtype.suite.TestSuiteInfo;
23 import com.android.tradefed.util.AaptParser;
24 import com.android.tradefed.util.AbiUtils;
25 import com.android.tradefed.util.FileUtil;
26 
27 import org.junit.Ignore;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 import org.junit.runners.JUnit4;
31 
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Map.Entry;
40 import java.util.Set;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43 import java.util.stream.Collectors;
44 
45 /**
46  * Tests to validate that the build is containing usable test artifact.
47  */
48 @RunWith(JUnit4.class)
49 public class ValidateTestsAbi {
50 
51     private static final Set<String> APK_EXCEPTIONS = new HashSet<>();
52     static {
53         /**
54          *  This particular module is shipping all its dependencies in all abis with prebuilt stuff.
55          *  Excluding it for now to have the test setup.
56          */
57         APK_EXCEPTIONS.add("CtsSplitApp");
58 
59         /**
60          *  This module tests for security vulnerabilities when installing attacker-devised APKs.
61          */
62         APK_EXCEPTIONS.add("CtsCorruptApkTests");
63 
64         /**
65          * This module tests for installations of packages that have only 32-bit native libraries
66          * and extract native libraries.
67          */
68         APK_EXCEPTIONS.add("CtsExtractNativeLibsAppTrue32");
69 
70         /**
71          * This module tests for installations of packages that have only 64-bit native libraries
72          * and extract native libraries.
73          */
74         APK_EXCEPTIONS.add("CtsExtractNativeLibsAppTrue64");
75         /**
76          * This module tests for installations of packages that have only 32-bit native libraries
77          * and embed native libraries.
78          */
79         APK_EXCEPTIONS.add("CtsExtractNativeLibsAppFalse32");
80 
81         /**
82          * This module tests for installations of packages that have only 64-bit native libraries
83          * and embed native libraries.
84          */
85         APK_EXCEPTIONS.add("CtsExtractNativeLibsAppFalse64");
86 
87         /**
88          * These apks are prebuilts needed for some tests
89          */
90         APK_EXCEPTIONS.add("CtsApkVerityTestAppPrebuilt");
91         APK_EXCEPTIONS.add("CtsApkVerityTestApp2Prebuilt");
92 
93         /**
94          * Data apk used by SimpleperfTestCases
95          */
96         APK_EXCEPTIONS.add("base");
97 
98         /**
99          * This module tests that packages with only 32-bit native libraries will receive a
100          * warning message when running on devices that support both 32-bit and 64-bit ABIs.
101          */
102         APK_EXCEPTIONS.add("CtsDeviceDeprecatedAbiApp");
103     }
104 
105     private static final Set<String> BINARY_EXCEPTIONS = new HashSet<>();
106     static {
107         /**
108          * These binaries are host side helpers, so we do not need to check them.
109          */
110         BINARY_EXCEPTIONS.add("sepolicy-analyze");
111         BINARY_EXCEPTIONS.add("avbtool");
112         BINARY_EXCEPTIONS.add("img2simg");
113         BINARY_EXCEPTIONS.add("initrd_bootconfig");
114         BINARY_EXCEPTIONS.add("lpmake");
115         BINARY_EXCEPTIONS.add("lpunpack");
116         BINARY_EXCEPTIONS.add("mk_payload");
117         BINARY_EXCEPTIONS.add("sign_virt_apex");
118         BINARY_EXCEPTIONS.add("simg2img");
119         BINARY_EXCEPTIONS.add("dtdiff");
120         BINARY_EXCEPTIONS.add("dtc");
121         BINARY_EXCEPTIONS.add("lz4");
122 
123         /**
124          * These binaries are testing components with no 32-bit variant, which
125          * means their dependent libraries by default will not have 32-bit
126          * variants on the device, and which gain no additional testing coverage
127          * by forcing those variants to be available.
128          */
129         BINARY_EXCEPTIONS.add("CtsInitTestCases");
130     }
131 
132     private static final String BINARY_EXCEPTIONS_REGEX [] = {
133         /**
134          * This regular expression matches any binary of the form 'CVE-xxxx-yyyyyy'.
135          * Hence this can be used for tests that build for either 32 bit or 64 bit only.
136          */
137         "^CVE-\\d{4}-.+$"
138     };
139 
140     private static final String[] BINARY_SUFFIX_EXCEPTIONS = {
141         /**
142          * All STS test binaries rvc+ are in the form of *_sts32 or *_sts64.
143          *
144          * Many STS binaries are only feasible on a specific bitness so STS
145          * pushes the appropriate binary to compatible devices.
146          */
147         "_sts32", "_sts64",
148     };
149 
150     /**
151      * Test that all apks have the same supported abis.
152      * Sometimes, if a module is missing LOCAL_MULTILIB := both, we will end up with only one of
153      * the two abis required and the second one will fail.
154      */
155     @Ignore // TODO(b/1555499)
156     @Test
testApksAbis()157     public void testApksAbis() throws IOException {
158         String ctsRoot = System.getProperty("CTS_ROOT");
159         File testcases = new File(ctsRoot, "/android-cts/testcases/");
160         if (!testcases.exists()) {
161             fail(String.format("%s does not exists", testcases));
162             return;
163         }
164         Set<File> listApks = FileUtil.findFilesObject(testcases, ".*\\.apk");
165         listApks.removeIf(
166                 a -> {for (String apk : APK_EXCEPTIONS) {
167                     if (a.getName().startsWith(apk)) {
168                         return true;
169                     }
170                 }
171                 return false;});
172         assertTrue(listApks.size() > 0);
173         int maxAbi = 0;
174         Map<String, Integer> apkToAbi = new HashMap<>();
175 
176         for (File testApk : listApks) {
177             AaptParser result = AaptParser.parse(testApk);
178             // Retry as we have seen flake with aapt sometimes.
179             if (result == null) {
180                 for (int i = 0; i < 2; i++) {
181                     result = AaptParser.parse(testApk);
182                     if (result != null) {
183                         break;
184                     }
185                 }
186                 // If still couldn't parse the apk
187                 if (result == null) {
188                     fail(String.format("Fail to run 'aapt dump badging %s'",
189                             testApk.getAbsolutePath()));
190                 }
191             }
192             // We only check the apk that have native code
193             if (!result.getNativeCode().isEmpty()) {
194                 List<String> supportedAbiApk = result.getNativeCode();
195                 Set<String> buildTarget = AbiUtils.getAbisForArch(
196                         TestSuiteInfo.getInstance().getTargetArchs().get(0));
197                 // first check, all the abis in the buildTarget are supported
198                 for (String abiBT : buildTarget) {
199                     Boolean findMatch = false;
200                     for (String abiApk : supportedAbiApk) {
201                         if (abiApk.equals(abiBT)) {
202                             findMatch = true;
203                             break;
204                         }
205                     }
206                     if (!findMatch) {
207                         fail(String.format("apk %s %s does not support our abis [%s]",
208                                 testApk.getName(), supportedAbiApk, buildTarget));
209                     }
210                 }
211                 apkToAbi.put(testApk.getName(), supportedAbiApk.size());
212                 maxAbi = Math.max(maxAbi, buildTarget.size());
213             }
214         }
215 
216         // We do a second pass to make sure nobody is short on abi
217         for (Entry<String, Integer> apk : apkToAbi.entrySet()) {
218             if (apk.getValue() < maxAbi) {
219                 fail(String.format("apk %s only has %s abi when it should have %s", apk.getKey(),
220                         apk.getValue(), maxAbi));
221             }
222         }
223     }
224 
225     /**
226      * Test that when CTS has multiple abis, we have binary for each ABI. In this case the abi will
227      * be the same with different bitness (only case supported by build system).
228      * <p/>
229      * If there is only one bitness, then we check that it's the right one.
230      */
231     @Test
testBinariesAbis()232     public void testBinariesAbis() throws IOException {
233         String ctsRoot = System.getProperty("CTS_ROOT");
234         File testcases = new File(ctsRoot, "/android-cts/testcases/");
235         if (!testcases.exists()) {
236             fail(String.format("%s does not exist", testcases));
237             return;
238         }
239         Set<File> listBinaries = FileUtil.findFilesObject(testcases, ".*");
240         listBinaries.removeIf(f -> {
241                 String name = f.getName();
242                 if (name.contains(".")) {
243                     return true;
244                 }
245                 if (BINARY_EXCEPTIONS.contains(name)) {
246                     return true;
247                 }
248                 for (String suffixException : BINARY_SUFFIX_EXCEPTIONS) {
249                     if (name.endsWith(suffixException)) {
250                         return true;
251                     }
252                 }
253                 if (f.isDirectory()) {
254                     return true;
255                 }
256                 if (!f.canExecute()) {
257                     return true;
258                 }
259                 try {
260                     // Ignore python binaries
261                     String content = FileUtil.readStringFromFile(f);
262                     if (content.startsWith("#!/usr/bin/env python")) {
263                         return true;
264                     }
265                     if (content.contains("mobly/__init__.py")) {
266                         return true;
267                     }
268                 } catch (IOException e) {
269                     throw new RuntimeException(e);
270                 }
271                 for(String pattern: BINARY_EXCEPTIONS_REGEX) {
272                     Matcher matcher = Pattern.compile(pattern).matcher(name);
273                     if (matcher.matches()) {
274                         return true;
275                     }
276                 }
277                 return false;
278         });
279         assertTrue(listBinaries.size() > 0);
280         List<String> orderedList = listBinaries.stream().map(f->f.getName()).collect(Collectors.toList());
281         // we sort to have binary starting with same name, next to each other. The last two
282         // characters of their name with be the bitness (32 or 64).
283         Collections.sort(orderedList);
284         Set<String> buildTarget = AbiUtils.getAbisForArch(
285                 TestSuiteInfo.getInstance().getTargetArchs().get(0));
286         // We expect one binary per abi of CTS, they should be appended with 32 or 64
287         for (int i = 0; i < orderedList.size(); i=i + buildTarget.size()) {
288             List<String> subSet = orderedList.subList(i, i + buildTarget.size());
289             if (subSet.size() > 1) {
290                 String base = subSet.get(0).substring(0, subSet.get(0).length() - 2);
291                 for (int j = 0; j < subSet.size(); j++) {
292                     assertEquals(base, subSet.get(j).substring(0, subSet.get(j).length() - 2));
293                 }
294             } else {
295                 String bitness = AbiUtils.getBitness(buildTarget.iterator().next());
296                 assertTrue(subSet.get(i).endsWith(bitness));
297             }
298         }
299     }
300 }
301