• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 android.signature.cts.api;
17 
18 import android.app.Instrumentation;
19 import android.os.Bundle;
20 import android.provider.Settings;
21 import android.signature.cts.ApiDocumentParser;
22 import android.signature.cts.ClassProvider;
23 import android.signature.cts.ExcludingClassProvider;
24 import android.signature.cts.ExpectedFailuresFilter;
25 import android.signature.cts.FailureType;
26 import android.signature.cts.JDiffClassDescription;
27 import android.signature.cts.ResultObserver;
28 import android.signature.cts.VirtualPath;
29 import android.util.Log;
30 import androidx.test.platform.app.InstrumentationRegistry;
31 import androidx.test.runner.AndroidJUnit4;
32 import com.android.compatibility.common.util.DynamicConfigDeviceSide;
33 import com.google.common.base.Suppliers;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.function.Supplier;
37 import java.util.stream.Stream;
38 import org.junit.AfterClass;
39 import org.junit.Before;
40 import org.junit.runner.RunWith;
41 
42 import static org.junit.Assert.assertEquals;
43 import static org.junit.Assert.assertNull;
44 
45 /**
46  * Base class for the signature tests.
47  */
48 @RunWith(AndroidJUnit4.class)
49 public abstract class AbstractApiTest {
50 
51     /**
52      * The name of the optional instrumentation option that contains the name of the dynamic config
53      * data set that contains the expected failures.
54      */
55     private static final String DYNAMIC_CONFIG_NAME_OPTION = "dynamic-config-name";
56 
57     private TestResultObserver mResultObserver;
58 
59     ClassProvider mClassProvider;
60 
61     /**
62      * The list of expected failures.
63      */
64     private Collection<String> expectedFailures = Collections.emptyList();
65 
66     @AfterClass
closeResourceStore()67     public static void closeResourceStore() {
68         ResourceStore.close();
69     }
70 
getInstrumentation()71     public Instrumentation getInstrumentation() {
72         return InstrumentationRegistry.getInstrumentation();
73     }
74 
getGlobalExemptions()75     protected String getGlobalExemptions() {
76         return Settings.Global.getString(
77                 getInstrumentation().getContext().getContentResolver(),
78                 Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS);
79     }
80 
getGlobalHiddenApiPolicy()81     protected String getGlobalHiddenApiPolicy() {
82         return Settings.Global.getString(
83                 getInstrumentation().getContext().getContentResolver(),
84                 Settings.Global.HIDDEN_API_POLICY);
85     }
86 
87     @Before
setUp()88     public void setUp() throws Exception {
89         mResultObserver = new TestResultObserver();
90 
91         // Get the arguments passed to the instrumentation.
92         Bundle instrumentationArgs = InstrumentationRegistry.getArguments();
93 
94         // Check that the device is in the correct state for running this test.
95         assertEquals(
96                 String.format("Device in bad state: %s is not as expected",
97                         Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS),
98                 getExpectedBlocklistExemptions(),
99                 getGlobalExemptions());
100         assertNull(
101                 String.format("Device in bad state: %s is not as expected",
102                         Settings.Global.HIDDEN_API_POLICY),
103                 getGlobalHiddenApiPolicy());
104 
105 
106         // Prepare for a class provider that loads classes from bootclasspath but filters
107         // out known inaccessible classes.
108         // Note that com.android.internal.R.* inner classes are also excluded as they are
109         // not part of API though exist in the runtime.
110         mClassProvider = new ExcludingClassProvider(
111                 new BootClassPathClassesProvider(),
112                 name -> name != null && name.startsWith("com.android.internal.R."));
113 
114         String dynamicConfigName = instrumentationArgs.getString(DYNAMIC_CONFIG_NAME_OPTION);
115         if (dynamicConfigName != null) {
116             // Get the DynamicConfig.xml contents and extract the expected failures list.
117             DynamicConfigDeviceSide dcds = new DynamicConfigDeviceSide(dynamicConfigName);
118             Collection<String> expectedFailures = dcds.getValues("expected_failures");
119             initExpectedFailures(expectedFailures);
120         }
121 
122         initializeFromArgs(instrumentationArgs);
123     }
124 
125     /**
126      * Initialize the expected failures.
127      *
128      * <p>Call from with {@link #setUp()}</p>
129      *
130      * @param expectedFailures the expected failures.
131      */
initExpectedFailures(Collection<String> expectedFailures)132     private void initExpectedFailures(Collection<String> expectedFailures) {
133         this.expectedFailures = expectedFailures;
134         String tag = getClass().getName();
135         Log.d(tag, "Expected failure count: " + expectedFailures.size());
136         for (String failure: expectedFailures) {
137             Log.d(tag, "Expected failure: \"" + failure + "\"");
138         }
139     }
140 
getExpectedBlocklistExemptions()141     protected String getExpectedBlocklistExemptions() {
142         return null;
143     }
144 
initializeFromArgs(Bundle instrumentationArgs)145     protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
146     }
147 
148     protected interface RunnableWithResultObserver {
run(ResultObserver observer)149         void run(ResultObserver observer) throws Exception;
150     }
151 
runWithTestResultObserver(RunnableWithResultObserver runnable)152     void runWithTestResultObserver(RunnableWithResultObserver runnable) {
153         runWithTestResultObserver(expectedFailures, runnable);
154     }
155 
runWithTestResultObserver( Collection<String> expectedFailures, RunnableWithResultObserver runnable)156     private void runWithTestResultObserver(
157             Collection<String> expectedFailures, RunnableWithResultObserver runnable) {
158         try {
159             ResultObserver observer = mResultObserver;
160             if (!expectedFailures.isEmpty()) {
161                 observer = new ExpectedFailuresFilter(observer, expectedFailures);
162             }
163             runnable.run(observer);
164         } catch (Error|Exception e) {
165             mResultObserver.notifyFailure(
166                     FailureType.CAUGHT_EXCEPTION,
167                     e.getClass().getName(),
168                     "Uncaught exception thrown by test",
169                     e);
170         }
171         mResultObserver.onTestComplete(); // Will throw is there are failures
172     }
173 
getSupplierOfAnOptionalCommaSeparatedListArgument(String key)174     static Supplier<String[]> getSupplierOfAnOptionalCommaSeparatedListArgument(String key) {
175         return Suppliers.memoize(() -> {
176             Bundle arguments = InstrumentationRegistry.getArguments();
177             return getCommaSeparatedListOptional(arguments, key);
178         })::get;
179     }
180 
getCommaSeparatedListOptional(Bundle instrumentationArgs, String key)181     static String[] getCommaSeparatedListOptional(Bundle instrumentationArgs, String key) {
182         String argument = instrumentationArgs.getString(key);
183         if (argument == null) {
184             return new String[0];
185         }
186         return argument.split(",");
187     }
188 
getSupplierOfAMandatoryCommaSeparatedListArgument(String key)189     static Supplier<String[]> getSupplierOfAMandatoryCommaSeparatedListArgument(String key) {
190         return Suppliers.memoize(() -> {
191             Bundle arguments = InstrumentationRegistry.getArguments();
192             return getCommaSeparatedListRequired(arguments, key);
193         })::get;
194     }
195 
196     static String[] getCommaSeparatedListRequired(Bundle instrumentationArgs, String key) {
197         String argument = instrumentationArgs.getString(key);
198         if (argument == null) {
199             throw new IllegalStateException("Could not find required argument '" + key + "'");
200         }
201         return argument.split(",");
202     }
203 
204     /**
205      * Create a stream of {@link JDiffClassDescription} by parsing a set of API resource files.
206      *
207      * @param apiDocumentParser the parser to use.
208      * @param apiResources the list of API resource files.
209      *
210      * @return the stream of {@link JDiffClassDescription}.
211      */
212     Stream<JDiffClassDescription> parseApiResourcesAsStream(
213             ApiDocumentParser apiDocumentParser, String[] apiResources) {
214         return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
215                 .flatMap(apiDocumentParser::parseAsStream);
216     }
217 
218     /**
219      * Retrieve a stream of {@link VirtualPath} from a list of API resource files.
220      *
221      * <p>Any zip files are flattened, i.e. if a resource name ends with {@code .zip} then it is
222      * unpacked into a temporary directory and the paths to the unpacked files are returned instead
223      * of the path to the zip file.</p>
224      *
225      * @param classLoader the {@link ClassLoader} from which the resources will be loaded.
226      * @param apiResources the list of API resource files.
227      *
228      * @return the stream of {@link VirtualPath}.
229      */
230     static Stream<VirtualPath> retrieveApiResourcesAsStream(
231             ClassLoader classLoader,
232             String[] apiResources) {
233         return Stream.of(apiResources)
234                 .flatMap(resourceName -> ResourceStore.readResource(classLoader, resourceName));
235     }
236 }
237