blob: 2d0f7196f590cae53e5a88be1f8bc419e74b7c89 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.signature.cts.api;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.SharedLibraryInfo;
import android.signature.cts.ApiComplianceChecker;
import android.signature.cts.ApiDocumentParser;
import android.signature.cts.JDiffClassDescription;
import android.signature.cts.VirtualPath;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.google.common.base.Suppliers;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import junitparams.naming.TestCaseName;
import org.junit.Assume;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Verifies that any shared library provided by this device and for which this test has a
* corresponding API specific file provides the expected API.
*
* <pre>This test relies on the AndroidManifest.xml file for the APK in which this is run having a
* {@code <uses-library>} entry for every shared library that provides an API that is contained
* within the shared-libs-all.api.zip supplied to this test.
*/
@RunWith(JUnitParamsRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SignatureMultiLibsTest extends AbstractSignatureTest {
protected static final Supplier<String[]> EXPECTED_API_FILES =
getSupplierOfAMandatoryCommaSeparatedListArgument(EXPECTED_API_FILES_ARG);
protected static final Supplier<String[]> PREVIOUS_API_FILES =
getSupplierOfAMandatoryCommaSeparatedListArgument(PREVIOUS_API_FILES_ARG);
private static final String TAG = SignatureMultiLibsTest.class.getSimpleName();
/**
* A memoized supplier of the list of shared libraries on the device.
*/
protected static final Supplier<Set<String>> AVAILABLE_SHARED_LIBRARIES =
Suppliers.memoize(SignatureMultiLibsTest::retrieveActiveSharedLibraries)::get;
private static final String SHARED_LIBRARY_LIST_RESOURCE_NAME = "shared-libs-names.txt";
/**
* Retrieve the names of the shared libraries that are active on the device.
*
* @return The set of shared library names.
*/
private static Set<String> retrieveActiveSharedLibraries() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
Context context = instrumentation.getTargetContext();
List<SharedLibraryInfo> sharedLibraries =
context.getPackageManager().getSharedLibraries(0);
Set<String> sharedLibraryNames = new TreeSet<>();
for (SharedLibraryInfo sharedLibrary : sharedLibraries) {
String name = sharedLibrary.getName();
sharedLibraryNames.add(name);
Log.d(TAG, String.format("Found library: %s%n", name));
}
return sharedLibraryNames;
}
/**
* A memoized supplier of the list of shared libraries that this can test.
*/
protected static final Supplier<List<String>> TESTABLE_SHARED_LIBRARIES =
Suppliers.memoize(SignatureMultiLibsTest::retrieveTestableSharedLibraries)::get;
/**
* Retrieve the names of the shared libraries that are testable by this test.
*
* @return The set of shared library names.
*/
private static List<String> retrieveTestableSharedLibraries() {
ClassLoader classLoader = SignatureMultiLibsTest.class.getClassLoader();
try (InputStream is = classLoader.getResourceAsStream(SHARED_LIBRARY_LIST_RESOURCE_NAME)) {
if (is == null) {
throw new RuntimeException(
"Resource " + SHARED_LIBRARY_LIST_RESOURCE_NAME + " could not be found");
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
return reader.lines()
.filter(line -> !line.isEmpty())
.sorted()
.collect(Collectors.toList());
}
} catch (IOException e) {
throw new RuntimeException("Could not retrieve testable shared libraries", e);
}
}
/**
* Convert the list of testable shared libraries into a form suitable for parameterizing a test.
*/
public Object[][] getTestableSharedLibraryParameters() {
List<String> libraries = TESTABLE_SHARED_LIBRARIES.get();
Object[][] params = new Object[libraries.size()][1];
for (int i = 0; i < libraries.size(); i++) {
String name = libraries.get(i);
TestableLibraryParameter param = new TestableLibraryParameter(name);
params[i][0] = param;
}
return params;
}
/**
* Skips the test if the supplied library is unavailable on the device.
*
* <p>If the library is unavailable then this throws an
* {@link org.junit.AssumptionViolatedException}.</p>
*
* @param library the name of the library that needs to be available.
*/
private void skipTestIfLibraryIsUnavailable(String library) {
Assume.assumeTrue("Shared library " + library + " is not available on this device",
AVAILABLE_SHARED_LIBRARIES.get().contains(library));
}
/**
* Return a stream of {@link JDiffClassDescription} that are expected to be provided by the
* shared libraries which are installed on this device.
*
* @param apiDocumentParser the parser to use.
* @param apiResources the list of API resource files.
* @param library the name of the library whose APIs should be parsed.
* @return a stream of {@link JDiffClassDescription}.
*/
private Stream<JDiffClassDescription> parseActiveSharedLibraryApis(
ApiDocumentParser apiDocumentParser, String[] apiResources, String library) {
return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
.filter(path -> {
String apiLibraryName = getLibraryNameFromPath(path);
return apiLibraryName.equals(library);
})
.flatMap(apiDocumentParser::parseAsStream);
}
/**
* Tests that each shared library's API matches its current API.
*
* <p>One test per shared library, checks the entire API, and then reports the complete list of
* failures.</p>
*/
@Test
// Parameterize this method with the set of testable shared libraries.
@Parameters(method = "getTestableSharedLibraryParameters")
// The test name is the method name followed by and _ and the shared library name, with .s
// replaced with _. e.g. testRuntimeCompatibilityWithCurrentApi_android_test_base.
@TestCaseName("{method}_{0}")
public void testRuntimeCompatibilityWithCurrentApi(TestableLibraryParameter parameter) {
String library = parameter.getName();
skipTestIfLibraryIsUnavailable(library);
runWithTestResultObserver(mResultObserver -> {
ApiComplianceChecker complianceChecker =
new ApiComplianceChecker(mResultObserver, mClassProvider);
// Load classes from any API files that form the base which the expected APIs extend.
loadBaseClasses(complianceChecker);
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
parseActiveSharedLibraryApis(apiDocumentParser, EXPECTED_API_FILES.get(), library)
.forEach(complianceChecker::checkSignatureCompliance);
// After done parsing all expected API files, perform any deferred checks.
complianceChecker.checkDeferred();
});
}
/**
* Tests that each shared library's API matches its previous APIs.
*
* <p>One test per shared library, checks the entire API, and then reports the complete list of
* failures.</p>
*/
@Test
// Parameterize this method with the set of testable shared libraries.
@Parameters(method = "getTestableSharedLibraryParameters")
// The test name is the method name followed by and _ and the shared library name, with .s
// replaced with _. e.g. testRuntimeCompatibilityWithPreviousApis_android_test_base.
@TestCaseName("{method}_{0}")
public void testRuntimeCompatibilityWithPreviousApis(TestableLibraryParameter parameter) {
String library = parameter.getName();
skipTestIfLibraryIsUnavailable(library);
runWithTestResultObserver(mResultObserver -> {
ApiComplianceChecker complianceChecker =
new ApiComplianceChecker(mResultObserver, mClassProvider);
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
parseActiveSharedLibraryApis(apiDocumentParser, PREVIOUS_API_FILES.get(), library)
.map(clazz -> clazz.setPreviousApiFlag(true))
.forEach(complianceChecker::checkSignatureCompliance);
// After done parsing all expected API files, perform any deferred checks.
complianceChecker.checkDeferred();
});
}
/**
* Get the library name from the API file's path.
*
* @param path the path of the API file.
* @return the library name for the API file.
*/
private String getLibraryNameFromPath(VirtualPath path) {
String name = path.toString();
return name.substring(name.lastIndexOf('/') + 1).split("-")[0];
}
/**
* A wrapper around a shared library name to ensure that its string representation is suitable
* for use in a parameterized test name, i.e. does not contain any characters that are not
* allowed in a test name by CTS/AndroidJUnitRunner.
*/
public static class TestableLibraryParameter {
private final String name;
private final String parameter;
public TestableLibraryParameter(String name) {
this.name = name;
this.parameter = name.replace('.', '_');
}
public String getName() {
return name;
}
@Override
public String toString() {
return parameter;
}
}
}