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.os.Bundle;
19 import android.provider.Settings;
20 import android.signature.cts.ApiDocumentParser;
21 import android.signature.cts.ClassProvider;
22 import android.signature.cts.ExcludingClassProvider;
23 import android.signature.cts.FailureType;
24 import android.signature.cts.JDiffClassDescription;
25 import android.signature.cts.VirtualPath;
26 import android.signature.cts.VirtualPath.LocalFilePath;
27 import android.signature.cts.VirtualPath.ResourcePath;
28 import android.util.Log;
29 
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.PrintWriter;
33 import java.io.StringWriter;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.util.stream.Stream;
37 import java.util.zip.ZipFile;
38 import repackaged.android.test.InstrumentationTestCase;
39 import repackaged.android.test.InstrumentationTestRunner;
40 
41 /**
42  */
43 public class AbstractApiTest extends InstrumentationTestCase {
44 
45     private static final String TAG = "SignatureTest";
46 
47     private TestResultObserver mResultObserver;
48 
49     ClassProvider mClassProvider;
50 
getGlobalExemptions()51     protected String getGlobalExemptions() {
52         return Settings.Global.getString(
53                 getInstrumentation().getContext().getContentResolver(),
54                 Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS);
55     }
56 
getGlobalHiddenApiPolicy()57     protected String getGlobalHiddenApiPolicy() {
58         return Settings.Global.getString(
59                 getInstrumentation().getContext().getContentResolver(),
60                 Settings.Global.HIDDEN_API_POLICY);
61     }
62 
63     @Override
setUp()64     protected void setUp() throws Exception {
65         super.setUp();
66         mResultObserver = new TestResultObserver();
67 
68         // Get the arguments passed to the instrumentation.
69         Bundle instrumentationArgs =
70                 ((InstrumentationTestRunner) getInstrumentation()).getArguments();
71 
72         // Check that the device is in the correct state for running this test.
73         assertEquals(
74                 String.format("Device in bad state: %s is not as expected",
75                         Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS),
76                 getExpectedBlocklistExemptions(),
77                 getGlobalExemptions());
78         assertEquals(
79                 String.format("Device in bad state: %s is not as expected",
80                         Settings.Global.HIDDEN_API_POLICY),
81                 null,
82                 getGlobalHiddenApiPolicy());
83 
84 
85         // Prepare for a class provider that loads classes from bootclasspath but filters
86         // out known inaccessible classes.
87         // Note that com.android.internal.R.* inner classes are also excluded as they are
88         // not part of API though exist in the runtime.
89         mClassProvider = new ExcludingClassProvider(
90                 new BootClassPathClassesProvider(),
91                 name -> name != null && name.startsWith("com.android.internal.R."));
92 
93         initializeFromArgs(instrumentationArgs);
94     }
95 
getExpectedBlocklistExemptions()96     protected String getExpectedBlocklistExemptions() {
97         return null;
98     }
99 
initializeFromArgs(Bundle instrumentationArgs)100     protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
101 
102     }
103 
104     protected interface RunnableWithTestResultObserver {
run(TestResultObserver observer)105         void run(TestResultObserver observer) throws Exception;
106     }
107 
runWithTestResultObserver(RunnableWithTestResultObserver runnable)108     void runWithTestResultObserver(RunnableWithTestResultObserver runnable) {
109         try {
110             runnable.run(mResultObserver);
111         } catch (Exception e) {
112             StringWriter writer = new StringWriter();
113             writer.write(e.toString());
114             writer.write("\n");
115             e.printStackTrace(new PrintWriter(writer));
116             mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, e.getClass().getName(),
117                     writer.toString());
118         }
119         mResultObserver.onTestComplete(); // Will throw is there are failures
120     }
121 
getCommaSeparatedList(Bundle instrumentationArgs, String key)122     static String[] getCommaSeparatedList(Bundle instrumentationArgs, String key) {
123         String argument = instrumentationArgs.getString(key);
124         if (argument == null) {
125             return new String[0];
126         }
127         return argument.split(",");
128     }
129 
readResource(String resourceName)130     private Stream<VirtualPath> readResource(String resourceName) {
131         try {
132             ResourcePath resourcePath =
133                     VirtualPath.get(getClass().getClassLoader(), resourceName);
134             if (resourceName.endsWith(".zip")) {
135                 // Extract to a temporary file and read from there.
136                 Path file = extractResourceToFile(resourceName, resourcePath.newInputStream());
137                 return flattenPaths(VirtualPath.get(file.toString()));
138             } else {
139                 return Stream.of(resourcePath);
140             }
141         } catch (IOException e) {
142             throw new RuntimeException(e);
143         }
144     }
145 
extractResourceToFile(String resourceName, InputStream is)146     Path extractResourceToFile(String resourceName, InputStream is) throws IOException {
147         Path tempDirectory = Files.createTempDirectory("signature");
148         Path file = tempDirectory.resolve(resourceName);
149         Log.i(TAG, "extractResourceToFile: extracting " + resourceName + " to " + file);
150         Files.copy(is, file);
151         is.close();
152         return file;
153     }
154 
155     /**
156      * Given a path in the local file system (possibly of a zip file) flatten it into a stream of
157      * virtual paths.
158      */
flattenPaths(LocalFilePath path)159     private Stream<VirtualPath> flattenPaths(LocalFilePath path) {
160         try {
161             if (path.toString().endsWith(".zip")) {
162                 return getZipEntryFiles(path);
163             } else {
164                 return Stream.of(path);
165             }
166         } catch (IOException e) {
167             throw new RuntimeException(e);
168         }
169     }
170 
parseApiResourcesAsStream( ApiDocumentParser apiDocumentParser, String[] apiResources)171     Stream<JDiffClassDescription> parseApiResourcesAsStream(
172             ApiDocumentParser apiDocumentParser, String[] apiResources) {
173         return Stream.of(apiResources)
174                 .flatMap(this::readResource)
175                 .flatMap(apiDocumentParser::parseAsStream);
176     }
177 
178     /**
179      * Get the zip entries that are files.
180      *
181      * @param path the path to the zip file.
182      * @return paths to zip entries
183      */
getZipEntryFiles(LocalFilePath path)184     protected Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
185         @SuppressWarnings("resource")
186         ZipFile zip = new ZipFile(path.toFile());
187         return zip.stream().map(entry -> VirtualPath.get(zip, entry));
188     }
189 }
190