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