1 /* 2 * Copyright (C) 2011 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 17 package android.signature.cts.api; 18 19 import android.app.Instrumentation; 20 import android.content.Context; 21 import android.content.pm.SharedLibraryInfo; 22 import android.signature.cts.ApiComplianceChecker; 23 import android.signature.cts.ApiDocumentParser; 24 import android.signature.cts.JDiffClassDescription; 25 import android.signature.cts.VirtualPath; 26 import android.util.Log; 27 import androidx.test.platform.app.InstrumentationRegistry; 28 import com.google.common.base.Suppliers; 29 import java.io.BufferedReader; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.InputStreamReader; 33 import java.util.List; 34 import java.util.Set; 35 import java.util.TreeSet; 36 import java.util.function.Supplier; 37 import java.util.stream.Collectors; 38 import java.util.stream.Stream; 39 import junitparams.JUnitParamsRunner; 40 import junitparams.Parameters; 41 import junitparams.naming.TestCaseName; 42 import org.junit.Assume; 43 import org.junit.FixMethodOrder; 44 import org.junit.Test; 45 import org.junit.runner.RunWith; 46 import org.junit.runners.MethodSorters; 47 48 /** 49 * Verifies that any shared library provided by this device and for which this test has a 50 * corresponding API specific file provides the expected API. 51 * 52 * <pre>This test relies on the AndroidManifest.xml file for the APK in which this is run having a 53 * {@code <uses-library>} entry for every shared library that provides an API that is contained 54 * within the shared-libs-all.api.zip supplied to this test. 55 */ 56 @RunWith(JUnitParamsRunner.class) 57 @FixMethodOrder(MethodSorters.NAME_ASCENDING) 58 public class SignatureMultiLibsTest extends AbstractSignatureTest { 59 60 protected static final Supplier<String[]> EXPECTED_API_FILES = 61 getSupplierOfAMandatoryCommaSeparatedListArgument(EXPECTED_API_FILES_ARG); 62 63 protected static final Supplier<String[]> PREVIOUS_API_FILES = 64 getSupplierOfAMandatoryCommaSeparatedListArgument(PREVIOUS_API_FILES_ARG); 65 66 private static final String TAG = SignatureMultiLibsTest.class.getSimpleName(); 67 68 /** 69 * A memoized supplier of the list of shared libraries on the device. 70 */ 71 protected static final Supplier<Set<String>> AVAILABLE_SHARED_LIBRARIES = 72 Suppliers.memoize(SignatureMultiLibsTest::retrieveActiveSharedLibraries)::get; 73 74 private static final String SHARED_LIBRARY_LIST_RESOURCE_NAME = "shared-libs-names.txt"; 75 76 /** 77 * Retrieve the names of the shared libraries that are active on the device. 78 * 79 * @return The set of shared library names. 80 */ retrieveActiveSharedLibraries()81 private static Set<String> retrieveActiveSharedLibraries() { 82 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 83 Context context = instrumentation.getTargetContext(); 84 85 List<SharedLibraryInfo> sharedLibraries = 86 context.getPackageManager().getSharedLibraries(0); 87 88 Set<String> sharedLibraryNames = new TreeSet<>(); 89 for (SharedLibraryInfo sharedLibrary : sharedLibraries) { 90 String name = sharedLibrary.getName(); 91 sharedLibraryNames.add(name); 92 Log.d(TAG, String.format("Found library: %s%n", name)); 93 } 94 95 return sharedLibraryNames; 96 } 97 98 /** 99 * A memoized supplier of the list of shared libraries that this can test. 100 */ 101 protected static final Supplier<List<String>> TESTABLE_SHARED_LIBRARIES = 102 Suppliers.memoize(SignatureMultiLibsTest::retrieveTestableSharedLibraries)::get; 103 104 /** 105 * Retrieve the names of the shared libraries that are testable by this test. 106 * 107 * @return The set of shared library names. 108 */ retrieveTestableSharedLibraries()109 private static List<String> retrieveTestableSharedLibraries() { 110 ClassLoader classLoader = SignatureMultiLibsTest.class.getClassLoader(); 111 try (InputStream is = classLoader.getResourceAsStream(SHARED_LIBRARY_LIST_RESOURCE_NAME)) { 112 if (is == null) { 113 throw new RuntimeException( 114 "Resource " + SHARED_LIBRARY_LIST_RESOURCE_NAME + " could not be found"); 115 } 116 117 try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { 118 return reader.lines() 119 .filter(line -> !line.isEmpty()) 120 .sorted() 121 .collect(Collectors.toList()); 122 } 123 } catch (IOException e) { 124 throw new RuntimeException("Could not retrieve testable shared libraries", e); 125 } 126 } 127 128 /** 129 * Convert the list of testable shared libraries into a form suitable for parameterizing a test. 130 */ getTestableSharedLibraryParameters()131 public Object[][] getTestableSharedLibraryParameters() { 132 List<String> libraries = TESTABLE_SHARED_LIBRARIES.get(); 133 Object[][] params = new Object[libraries.size()][1]; 134 for (int i = 0; i < libraries.size(); i++) { 135 String name = libraries.get(i); 136 TestableLibraryParameter param = new TestableLibraryParameter(name); 137 params[i][0] = param; 138 } 139 return params; 140 } 141 142 /** 143 * Skips the test if the supplied library is unavailable on the device. 144 * 145 * <p>If the library is unavailable then this throws an 146 * {@link org.junit.AssumptionViolatedException}.</p> 147 * 148 * @param library the name of the library that needs to be available. 149 */ skipTestIfLibraryIsUnavailable(String library)150 private void skipTestIfLibraryIsUnavailable(String library) { 151 Assume.assumeTrue("Shared library " + library + " is not available on this device", 152 AVAILABLE_SHARED_LIBRARIES.get().contains(library)); 153 } 154 155 /** 156 * Return a stream of {@link JDiffClassDescription} that are expected to be provided by the 157 * shared libraries which are installed on this device. 158 * 159 * @param apiDocumentParser the parser to use. 160 * @param apiResources the list of API resource files. 161 * @param library the name of the library whose APIs should be parsed. 162 * @return a stream of {@link JDiffClassDescription}. 163 */ parseActiveSharedLibraryApis( ApiDocumentParser apiDocumentParser, String[] apiResources, String library)164 private Stream<JDiffClassDescription> parseActiveSharedLibraryApis( 165 ApiDocumentParser apiDocumentParser, String[] apiResources, String library) { 166 167 return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources) 168 .filter(path -> { 169 String apiLibraryName = getLibraryNameFromPath(path); 170 return apiLibraryName.equals(library); 171 }) 172 .flatMap(apiDocumentParser::parseAsStream); 173 } 174 175 /** 176 * Tests that each shared library's API matches its current API. 177 * 178 * <p>One test per shared library, checks the entire API, and then reports the complete list of 179 * failures.</p> 180 */ 181 @Test 182 // Parameterize this method with the set of testable shared libraries. 183 @Parameters(method = "getTestableSharedLibraryParameters") 184 // The test name is the method name followed by and _ and the shared library name, with .s 185 // replaced with _. e.g. testRuntimeCompatibilityWithCurrentApi_android_test_base. 186 @TestCaseName("{method}_{0}") testRuntimeCompatibilityWithCurrentApi(TestableLibraryParameter parameter)187 public void testRuntimeCompatibilityWithCurrentApi(TestableLibraryParameter parameter) { 188 String library = parameter.getName(); 189 skipTestIfLibraryIsUnavailable(library); 190 runWithTestResultObserver(mResultObserver -> { 191 ApiComplianceChecker complianceChecker = 192 new ApiComplianceChecker(mResultObserver, mClassProvider); 193 194 // Load classes from any API files that form the base which the expected APIs extend. 195 loadBaseClasses(complianceChecker); 196 197 ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG); 198 199 parseActiveSharedLibraryApis(apiDocumentParser, EXPECTED_API_FILES.get(), library) 200 .forEach(complianceChecker::checkSignatureCompliance); 201 202 // After done parsing all expected API files, perform any deferred checks. 203 complianceChecker.checkDeferred(); 204 }); 205 } 206 207 /** 208 * Tests that each shared library's API matches its previous APIs. 209 * 210 * <p>One test per shared library, checks the entire API, and then reports the complete list of 211 * failures.</p> 212 */ 213 @Test 214 // Parameterize this method with the set of testable shared libraries. 215 @Parameters(method = "getTestableSharedLibraryParameters") 216 // The test name is the method name followed by and _ and the shared library name, with .s 217 // replaced with _. e.g. testRuntimeCompatibilityWithPreviousApis_android_test_base. 218 @TestCaseName("{method}_{0}") testRuntimeCompatibilityWithPreviousApis(TestableLibraryParameter parameter)219 public void testRuntimeCompatibilityWithPreviousApis(TestableLibraryParameter parameter) { 220 String library = parameter.getName(); 221 skipTestIfLibraryIsUnavailable(library); 222 runWithTestResultObserver(mResultObserver -> { 223 ApiComplianceChecker complianceChecker = 224 new ApiComplianceChecker(mResultObserver, mClassProvider); 225 226 ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG); 227 228 parseActiveSharedLibraryApis(apiDocumentParser, PREVIOUS_API_FILES.get(), library) 229 .map(clazz -> clazz.setPreviousApiFlag(true)) 230 .forEach(complianceChecker::checkSignatureCompliance); 231 232 // After done parsing all expected API files, perform any deferred checks. 233 complianceChecker.checkDeferred(); 234 }); 235 } 236 237 /** 238 * Get the library name from the API file's path. 239 * 240 * @param path the path of the API file. 241 * @return the library name for the API file. 242 */ getLibraryNameFromPath(VirtualPath path)243 private String getLibraryNameFromPath(VirtualPath path) { 244 String name = path.toString(); 245 return name.substring(name.lastIndexOf('/') + 1).split("-")[0]; 246 } 247 248 /** 249 * A wrapper around a shared library name to ensure that its string representation is suitable 250 * for use in a parameterized test name, i.e. does not contain any characters that are not 251 * allowed in a test name by CTS/AndroidJUnitRunner. 252 */ 253 public static class TestableLibraryParameter { 254 private final String name; 255 private final String parameter; 256 TestableLibraryParameter(String name)257 public TestableLibraryParameter(String name) { 258 this.name = name; 259 this.parameter = name.replace('.', '_'); 260 } 261 getName()262 public String getName() { 263 return name; 264 } 265 266 @Override toString()267 public String toString() { 268 return parameter; 269 } 270 } 271 } 272