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