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