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