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 
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.junit.runners.JUnit4;
29 
30 import java.io.File;
31 import java.io.FilenameFilter;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.Set;
40 
41 /**
42  * Tests to validate that the build is containing usable test artifact.
43  */
44 @RunWith(JUnit4.class)
45 public class ValidateTestsAbi {
46 
47     private static final Set<String> MODULE_EXCEPTIONS = new HashSet<>();
48     static {
49         /**
50          *  This particular module is shipping all it's dependencies in all abis with prebuilt stuff.
51          *  Excluding it for now to have the test setup.
52          */
53         MODULE_EXCEPTIONS.add("CtsSplitApp");
54 
55         /**
56          *  This module tests for security vulnerabilities when installing attacker-devised APKs.
57          */
58         MODULE_EXCEPTIONS.add("CtsCorruptApkTests");
59 
60         /**
61          * This module tests for installations of packages that have only 32-bit native libraries.
62          */
63         MODULE_EXCEPTIONS.add("CtsExtractNativeLibsAppTrue32");
64 
65         /**
66          * This module tests for installations of packages that have only 64-bit native libraries.
67          */
68         MODULE_EXCEPTIONS.add("CtsExtractNativeLibsAppTrue64");
69     }
70 
71     private static final Set<String> BINARY_EXCEPTIONS = new HashSet<>();
72     static {
73         /**
74          * This binary is a host side helper, so we do not need to check it.
75          */
76         BINARY_EXCEPTIONS.add("sepolicy-analyze");
77     }
78 
79     /**
80      * Test that all apks have the same supported abis.
81      * Sometimes, if a module is missing LOCAL_MULTILIB := both, we will end up with only one of
82      * the two abis required and the second one will fail.
83      */
84     @Test
testApksAbis()85     public void testApksAbis() {
86         String ctsRoot = System.getProperty("CTS_ROOT");
87         File testcases = new File(ctsRoot, "/android-cts/testcases/");
88         if (!testcases.exists()) {
89             fail(String.format("%s does not exists", testcases));
90             return;
91         }
92         File[] listApks = testcases.listFiles(new FilenameFilter() {
93             @Override
94             public boolean accept(File dir, String name) {
95                 for (String module : MODULE_EXCEPTIONS) {
96                     if (name.startsWith(module)) {
97                         return false;
98                     }
99                 }
100 
101                 return name.endsWith(".apk");
102             }
103         });
104         assertTrue(listApks.length > 0);
105         int maxAbi = 0;
106         Map<String, Integer> apkToAbi = new HashMap<>();
107 
108         for (File testApk : listApks) {
109             AaptParser result = AaptParser.parse(testApk);
110             // Retry as we have seen flake with aapt sometimes.
111             if (result == null) {
112                 for (int i = 0; i < 2; i++) {
113                     result = AaptParser.parse(testApk);
114                     if (result != null) {
115                         break;
116                     }
117                 }
118                 // If still couldn't parse the apk
119                 if (result == null) {
120                     fail(String.format("Fail to run 'aapt dump badging %s'",
121                             testApk.getAbsolutePath()));
122                 }
123             }
124             // We only check the apk that have native code
125             if (!result.getNativeCode().isEmpty()) {
126                 List<String> supportedAbiApk = result.getNativeCode();
127                 Set<String> buildTarget = AbiUtils.getAbisForArch(
128                         TestSuiteInfo.getInstance().getTargetArchs().get(0));
129                 // first check, all the abis are supported
130                 for (String abi : supportedAbiApk) {
131                     if (!buildTarget.contains(abi)) {
132                         fail(String.format("apk %s %s does not support our abis [%s]",
133                                 testApk.getName(), supportedAbiApk, buildTarget));
134                     }
135                 }
136                 apkToAbi.put(testApk.getName(), supportedAbiApk.size());
137                 maxAbi = Math.max(maxAbi, supportedAbiApk.size());
138             }
139         }
140 
141         // We do a second pass to make sure nobody is short on abi
142         for (Entry<String, Integer> apk : apkToAbi.entrySet()) {
143             if (apk.getValue() < maxAbi) {
144                 fail(String.format("apk %s only has %s abi when it should have %s", apk.getKey(),
145                         apk.getValue(), maxAbi));
146             }
147         }
148     }
149 
150     /**
151      * Test that when CTS has multiple abis, we have binary for each ABI. In this case the abi will
152      * be the same with different bitness (only case supported by build system).
153      * <p/>
154      * If there is only one bitness, then we check that it's the right one.
155      */
156     @Test
testBinariesAbis()157     public void testBinariesAbis() {
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 exist", testcases));
162             return;
163         }
164         String[] listBinaries = testcases.list(new FilenameFilter() {
165             @Override
166             public boolean accept(File dir, String name) {
167                 if (name.contains(".")) {
168                     return false;
169                 }
170                 if (BINARY_EXCEPTIONS.contains(name)) {
171                     return false;
172                 }
173                 File file = new File(dir, name);
174                 if (file.isDirectory()) {
175                     return false;
176                 }
177                 if (!file.canExecute()) {
178                     return false;
179                 }
180                 return true;
181             }
182         });
183         assertTrue(listBinaries.length > 0);
184         List<String> orderedList = Arrays.asList(listBinaries);
185         // we sort to have binary starting with same name, next to each other. The last two
186         // characters of their name with be the bitness (32 or 64).
187         Collections.sort(orderedList);
188         Set<String> buildTarget = AbiUtils.getAbisForArch(
189                 TestSuiteInfo.getInstance().getTargetArchs().get(0));
190         // We expect one binary per abi of CTS, they should be appended with 32 or 64
191         for (int i = 0; i < orderedList.size(); i=i + buildTarget.size()) {
192             List<String> subSet = orderedList.subList(i, i + buildTarget.size());
193             if (subSet.size() > 1) {
194                 String base = subSet.get(0).substring(0, subSet.get(0).length() - 2);
195                 for (int j = 0; j < subSet.size(); j++) {
196                     assertEquals(base, subSet.get(j).substring(0, subSet.get(j).length() - 2));
197                 }
198             } else {
199                 String bitness = AbiUtils.getBitness(buildTarget.iterator().next());
200                 assertTrue(subSet.get(i).endsWith(bitness));
201             }
202         }
203     }
204 }
205