1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 5 package com.android.tools.r8; 6 7 import static com.android.tools.r8.dex.Constants.ANDROID_K_API; 8 import static com.android.tools.r8.dex.Constants.ANDROID_O_API; 9 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; 10 import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION; 11 import static org.junit.Assert.assertEquals; 12 import static org.junit.Assert.assertTrue; 13 14 import com.android.tools.r8.ToolHelper.DexVm; 15 import com.android.tools.r8.errors.CompilationError; 16 import com.android.tools.r8.utils.DexInspector; 17 import com.android.tools.r8.utils.DexInspector.FoundClassSubject; 18 import com.android.tools.r8.utils.DexInspector.FoundMethodSubject; 19 import com.android.tools.r8.utils.DexInspector.InstructionSubject; 20 import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject; 21 import com.android.tools.r8.utils.InternalOptions; 22 import com.android.tools.r8.utils.OffOrAuto; 23 import com.google.common.collect.ImmutableList; 24 import com.google.common.collect.ImmutableMap; 25 import java.nio.file.Path; 26 import java.nio.file.Paths; 27 import java.util.ArrayList; 28 import java.util.Iterator; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.function.Consumer; 32 import java.util.function.Predicate; 33 import java.util.function.UnaryOperator; 34 import org.junit.Assert; 35 import org.junit.Rule; 36 import org.junit.Test; 37 import org.junit.rules.ExpectedException; 38 import org.junit.rules.TemporaryFolder; 39 40 public abstract class RunExamplesAndroidOTest<B> { 41 static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR; 42 43 abstract class TestRunner { 44 final String testName; 45 final String packageName; 46 final String mainClass; 47 48 final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>(); 49 final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>(); 50 final List<UnaryOperator<B>> builderTransformations = new ArrayList<>(); 51 TestRunner(String testName, String packageName, String mainClass)52 TestRunner(String testName, String packageName, String mainClass) { 53 this.testName = testName; 54 this.packageName = packageName; 55 this.mainClass = mainClass; 56 } 57 withDexCheck(Consumer<DexInspector> check)58 TestRunner withDexCheck(Consumer<DexInspector> check) { 59 dexInspectorChecks.add(check); 60 return this; 61 } 62 withClassCheck(Consumer<FoundClassSubject> check)63 TestRunner withClassCheck(Consumer<FoundClassSubject> check) { 64 withDexCheck(inspector -> inspector.forAllClasses(check)); 65 return this; 66 } 67 withMethodCheck(Consumer<FoundMethodSubject> check)68 TestRunner withMethodCheck(Consumer<FoundMethodSubject> check) { 69 withClassCheck(clazz -> clazz.forAllMethods(check)); 70 return this; 71 } 72 73 <T extends InstructionSubject> TestRunner withInstructionCheck(Predicate<InstructionSubject> filter, Consumer<T> check)74 withInstructionCheck(Predicate<InstructionSubject> filter, Consumer<T> check) { 75 withMethodCheck(method -> { 76 if (method.isAbstract()) { 77 return; 78 } 79 Iterator<T> iterator = method.iterateInstructions(filter); 80 while (iterator.hasNext()) { 81 check.accept(iterator.next()); 82 } 83 }); 84 return this; 85 } 86 withOptionConsumer(Consumer<InternalOptions> consumer)87 TestRunner withOptionConsumer(Consumer<InternalOptions> consumer) { 88 optionConsumers.add(consumer); 89 return this; 90 } 91 withInterfaceMethodDesugaring(OffOrAuto behavior)92 TestRunner withInterfaceMethodDesugaring(OffOrAuto behavior) { 93 return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior); 94 } 95 withTryWithResourcesDesugaring(OffOrAuto behavior)96 TestRunner withTryWithResourcesDesugaring(OffOrAuto behavior) { 97 return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior); 98 } 99 withBuilderTransformation(UnaryOperator<B> builderTransformation)100 TestRunner withBuilderTransformation(UnaryOperator<B> builderTransformation) { 101 builderTransformations.add(builderTransformation); 102 return this; 103 } 104 combinedOptionConsumer(InternalOptions options)105 void combinedOptionConsumer(InternalOptions options) { 106 for (Consumer<InternalOptions> consumer : optionConsumers) { 107 consumer.accept(options); 108 } 109 } 110 run()111 void run() throws Throwable { 112 if (compilationErrorExpected(testName)) { 113 thrown.expect(CompilationError.class); 114 } 115 116 String qualifiedMainClass = packageName + "." + mainClass; 117 Path inputFile = Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION); 118 Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION); 119 120 build(inputFile, out); 121 122 if (!ToolHelper.artSupported()) { 123 return; 124 } 125 126 boolean expectedToFail = expectedToFail(testName); 127 if (expectedToFail) { 128 thrown.expect(Throwable.class); 129 } 130 131 if (!dexInspectorChecks.isEmpty()) { 132 DexInspector inspector = new DexInspector(out); 133 for (Consumer<DexInspector> check : dexInspectorChecks) { 134 check.accept(inspector); 135 } 136 } 137 138 String output = ToolHelper.runArtNoVerificationErrors(out.toString(), qualifiedMainClass); 139 if (!expectedToFail) { 140 ToolHelper.ProcessResult javaResult = 141 ToolHelper.runJava(ImmutableList.of(inputFile.toString()), qualifiedMainClass); 142 assertEquals("JVM run failed", javaResult.exitCode, 0); 143 assertTrue( 144 "JVM output does not match art output.\n\tjvm: " 145 + javaResult.stdout 146 + "\n\tart: " 147 + output, 148 output.equals(javaResult.stdout)); 149 } 150 } 151 withMinApiLevel(int minApiLevel)152 abstract TestRunner withMinApiLevel(int minApiLevel); 153 build(Path inputFile, Path out)154 abstract void build(Path inputFile, Path out) throws Throwable; 155 } 156 157 private static List<String> compilationErrorExpected = 158 ImmutableList.of( 159 "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk"); 160 161 private static Map<DexVm, List<String>> failsOn = 162 ImmutableMap.of( 163 DexVm.ART_4_4_4, ImmutableList.of( 164 // API not supported 165 "paramnames", 166 "repeat_annotations_new_api", 167 // Dex version not supported 168 "invokepolymorphic", 169 "invokecustom" 170 ), 171 DexVm.ART_5_1_1, ImmutableList.of( 172 // API not supported 173 "paramnames", 174 "repeat_annotations_new_api", 175 // Dex version not supported 176 "invokepolymorphic", 177 "invokecustom" 178 ), 179 DexVm.ART_6_0_1, ImmutableList.of( 180 // API not supported 181 "paramnames", 182 "repeat_annotations_new_api", 183 // Dex version not supported 184 "invokepolymorphic", 185 "invokecustom" 186 ), 187 DexVm.ART_7_0_0, ImmutableList.of( 188 // API not supported 189 "paramnames", 190 // Dex version not supported 191 "invokepolymorphic", 192 "invokecustom" 193 ), 194 DexVm.ART_DEFAULT, ImmutableList.of( 195 ) 196 ); 197 198 @Rule 199 public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); 200 201 @Rule 202 public ExpectedException thrown = ExpectedException.none(); 203 failsOn(Map<ToolHelper.DexVm, List<String>> failsOn, String name)204 boolean failsOn(Map<ToolHelper.DexVm, List<String>> failsOn, String name) { 205 return failsOn.containsKey(ToolHelper.getDexVm()) 206 && failsOn.get(ToolHelper.getDexVm()).contains(name); 207 } 208 expectedToFail(String name)209 boolean expectedToFail(String name) { 210 return failsOn(failsOn, name); 211 } 212 compilationErrorExpected(String testName)213 boolean compilationErrorExpected(String testName) { 214 return compilationErrorExpected.contains(testName); 215 } 216 217 @Test invokeCustom()218 public void invokeCustom() throws Throwable { 219 test("invokecustom", "invokecustom", "InvokeCustom") 220 .withMinApiLevel(ANDROID_O_API) 221 .run(); 222 } 223 224 @Test invokeCustomErrorDueToMinSdk()225 public void invokeCustomErrorDueToMinSdk() throws Throwable { 226 test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom") 227 .withMinApiLevel(25) 228 .run(); 229 } 230 231 @Test invokePolymorphic()232 public void invokePolymorphic() throws Throwable { 233 test("invokepolymorphic", "invokepolymorphic", "InvokePolymorphic") 234 .withMinApiLevel(ANDROID_O_API) 235 .run(); 236 } 237 238 @Test invokePolymorphicErrorDueToMinSdk()239 public void invokePolymorphicErrorDueToMinSdk() throws Throwable { 240 test("invokepolymorphic-error-due-to-min-sdk", "invokepolymorphic", "InvokePolymorphic") 241 .withMinApiLevel(25) 242 .run(); 243 } 244 245 @Test lambdaDesugaring()246 public void lambdaDesugaring() throws Throwable { 247 test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring") 248 .withMinApiLevel(ANDROID_K_API) 249 .run(); 250 } 251 252 @Test lambdaDesugaringNPlus()253 public void lambdaDesugaringNPlus() throws Throwable { 254 test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods") 255 .withMinApiLevel(ANDROID_K_API) 256 .withInterfaceMethodDesugaring(OffOrAuto.Auto) 257 .run(); 258 } 259 260 @Test lambdaDesugaringValueAdjustments()261 public void lambdaDesugaringValueAdjustments() throws Throwable { 262 test("lambdadesugaring-value-adjustments", "lambdadesugaring", "ValueAdjustments") 263 .withMinApiLevel(ANDROID_K_API) 264 .run(); 265 } 266 267 @Test paramNames()268 public void paramNames() throws Throwable { 269 test("paramnames", "paramnames", "ParameterNames") 270 .withMinApiLevel(26) 271 .withOptionConsumer((internalOptions) -> internalOptions.allowParameterName = true) 272 .run(); 273 } 274 275 @Test repeatAnnotationsNewApi()276 public void repeatAnnotationsNewApi() throws Throwable { 277 // No need to specify minSdk as repeat annotations are handled by javac and we do not have 278 // to do anything to support them. The library methods to access them just have to be in 279 // the system. 280 test("repeat_annotations_new_api", "repeat_annotations", "RepeatAnnotationsNewApi").run(); 281 } 282 283 @Test repeatAnnotations()284 public void repeatAnnotations() throws Throwable { 285 // No need to specify minSdk as repeat annotations are handled by javac and we do not have 286 // to do anything to support them. The library methods to access them just have to be in 287 // the system. 288 test("repeat_annotations", "repeat_annotations", "RepeatAnnotations").run(); 289 } 290 291 @Test testTryWithResources()292 public void testTryWithResources() throws Throwable { 293 test("try-with-resources-simplified", "trywithresources", "TryWithResourcesNotDesugaredTests") 294 .withTryWithResourcesDesugaring(OffOrAuto.Off) 295 .run(); 296 } 297 298 @Test testTryWithResourcesDesugared()299 public void testTryWithResourcesDesugared() throws Throwable { 300 test("try-with-resources-simplified", "trywithresources", "TryWithResourcesDesugaredTests") 301 .withTryWithResourcesDesugaring(OffOrAuto.Auto) 302 .withInstructionCheck(InstructionSubject::isInvoke, 303 (InvokeInstructionSubject invoke) -> { 304 Assert.assertFalse(invoke.invokedMethod().name.toString().equals("addSuppressed")); 305 Assert.assertFalse(invoke.invokedMethod().name.toString().equals("getSuppressed")); 306 }) 307 .run(); 308 } 309 test(String testName, String packageName, String mainClass)310 abstract TestRunner test(String testName, String packageName, String mainClass); 311 } 312