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