| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | package com.android.tools.r8; | 
 |  | 
 | import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; | 
 | import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION; | 
 | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; | 
 | import static org.hamcrest.MatcherAssert.assertThat; | 
 | import static org.junit.Assert.assertEquals; | 
 | import static org.junit.Assert.assertTrue; | 
 | import static org.junit.Assume.assumeFalse; | 
 |  | 
 | import com.android.tools.r8.ToolHelper.DexVm; | 
 | import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter; | 
 | import com.android.tools.r8.utils.AndroidApiLevel; | 
 | import com.android.tools.r8.utils.InternalOptions; | 
 | import com.android.tools.r8.utils.TestDescriptionWatcher; | 
 | import com.android.tools.r8.utils.codeinspector.ClassSubject; | 
 | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
 | import com.android.tools.r8.utils.codeinspector.MethodSubject; | 
 | import com.google.common.collect.ImmutableList; | 
 | import com.google.common.collect.ImmutableMap; | 
 | import java.io.IOException; | 
 | import java.nio.file.Path; | 
 | import java.nio.file.Paths; | 
 | import java.util.ArrayList; | 
 | import java.util.Arrays; | 
 | import java.util.List; | 
 | import java.util.Map; | 
 | import java.util.function.Consumer; | 
 | import java.util.function.UnaryOperator; | 
 | import java.util.stream.Collectors; | 
 | import org.junit.Rule; | 
 | import org.junit.Test; | 
 | import org.junit.rules.ExpectedException; | 
 | import org.junit.rules.TemporaryFolder; | 
 |  | 
 | public abstract class RunExamplesJava9Test | 
 |     <B extends BaseCommand.Builder<? extends BaseCommand, B>> { | 
 |  | 
 |   private static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_JAVA9_BUILD_DIR; | 
 |  | 
 |   abstract class TestRunner<C extends TestRunner<C>> { | 
 |     final String testName; | 
 |     final String packageName; | 
 |     final String mainClass; | 
 |     final List<String> args = new ArrayList<>(); | 
 |  | 
 |     Integer androidJarVersion = null; | 
 |  | 
 |     final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>(); | 
 |     final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>(); | 
 |     final List<UnaryOperator<B>> builderTransformations = new ArrayList<>(); | 
 |  | 
 |     TestRunner(String testName, String packageName, String mainClass) { | 
 |       this.testName = testName; | 
 |       this.packageName = packageName; | 
 |       this.mainClass = mainClass; | 
 |     } | 
 |  | 
 |     abstract C self(); | 
 |  | 
 |     C withDexCheck(Consumer<CodeInspector> check) { | 
 |       dexInspectorChecks.add(check); | 
 |       return self(); | 
 |     } | 
 |  | 
 |     C withArg(String arg) { | 
 |       args.add(arg); | 
 |       return self(); | 
 |     } | 
 |  | 
 |     void combinedOptionConsumer(InternalOptions options) { | 
 |       for (Consumer<InternalOptions> consumer : optionConsumers) { | 
 |         consumer.accept(options); | 
 |       } | 
 |     } | 
 |  | 
 |     C withBuilderTransformation(UnaryOperator<B> builderTransformation) { | 
 |       builderTransformations.add(builderTransformation); | 
 |       return self(); | 
 |     } | 
 |  | 
 |     Path build() throws Throwable { | 
 |       Path inputFile = getInputJar(); | 
 |       Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION); | 
 |  | 
 |       build(inputFile, out); | 
 |       return out; | 
 |     } | 
 |  | 
 |     Path getInputJar() { | 
 |       return Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION); | 
 |     } | 
 |  | 
 |     void run() throws Throwable { | 
 |       if (minSdkErrorExpected(testName)) { | 
 |         thrown.expect(CompilationFailedException.class); | 
 |       } | 
 |  | 
 |       String qualifiedMainClass = packageName + "." + mainClass; | 
 |       Path inputFile = getInputJar(); | 
 |       Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION); | 
 |  | 
 |       build(inputFile, out); | 
 |  | 
 |       if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) { | 
 |         return; | 
 |       } | 
 |  | 
 |       if (!dexInspectorChecks.isEmpty()) { | 
 |         CodeInspector inspector = new CodeInspector(out); | 
 |         for (Consumer<CodeInspector> check : dexInspectorChecks) { | 
 |           check.accept(inspector); | 
 |         } | 
 |       } | 
 |  | 
 |       execute(testName, qualifiedMainClass, new Path[]{inputFile}, new Path[]{out}, args); | 
 |     } | 
 |  | 
 |     abstract C withMinApiLevel(int minApiLevel); | 
 |  | 
 |     C withKeepAll() { | 
 |       return self(); | 
 |     } | 
 |  | 
 |     C withAndroidJar(int androidJarVersion) { | 
 |       assert this.androidJarVersion == null; | 
 |       this.androidJarVersion = androidJarVersion; | 
 |       return self(); | 
 |     } | 
 |  | 
 |     abstract void build(Path inputFile, Path out) throws Throwable; | 
 |   } | 
 |  | 
 |   private static List<String> minSdkErrorExpected = | 
 |       ImmutableList.of("varhandle-error-due-to-min-sdk"); | 
 |  | 
 |   private static Map<DexVm.Version, List<String>> failsOn; | 
 |  | 
 |   static { | 
 |     ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder(); | 
 |     builder | 
 |         .put(DexVm.Version.V4_0_4, ImmutableList.of( | 
 |             "native-private-interface-methods", // Dex version not supported | 
 |             "varhandle" | 
 |         )) | 
 |         .put(DexVm.Version.V4_4_4, ImmutableList.of( | 
 |             "native-private-interface-methods", // Dex version not supported | 
 |             "varhandle" | 
 |         )) | 
 |         .put(DexVm.Version.V5_1_1, ImmutableList.of( | 
 |             "native-private-interface-methods", // Dex version not supported | 
 |             "varhandle" | 
 |         )) | 
 |         .put(DexVm.Version.V6_0_1, ImmutableList.of( | 
 |             "native-private-interface-methods", // Dex version not supported | 
 |             "varhandle" | 
 |         )) | 
 |         .put(DexVm.Version.V7_0_0, ImmutableList.of( | 
 |             // Dex version not supported | 
 |             "varhandle" | 
 |         )) | 
 |         .put(DexVm.Version.V8_1_0, ImmutableList.of( | 
 |             // Dex version not supported | 
 |             "varhandle" | 
 |         )) | 
 |         .put(DexVm.Version.DEFAULT, ImmutableList.of( | 
 |             // TODO(b/72536415): Update runtime when the support will be ready | 
 |             "varhandle" | 
 |         )); | 
 |     failsOn = builder.build(); | 
 |   } | 
 |  | 
 |   // Defines methods failing on JVM, specifies the output to be used for comparison. | 
 |   private static Map<String, String> expectedJvmResult = | 
 |       ImmutableMap.of( | 
 |           "twr-close-resource", | 
 |           "A\nE\nG\nH\nI\nJ\nK\n" | 
 |               + "iA\niE\niG\niH\niI\niJ\niK\n" | 
 |               + "1\n2\n3\n4\n5\n6\n7\n8\n99\n" | 
 |               + "i1\ni2\ni3\ni4\ni5\ni6\ni7\ni8\ni99\n", | 
 |           "native-private-interface-methods", | 
 |           "0: s>i>a\n" | 
 |               + "1: d>i>s>i>a\n" | 
 |               + "2: l>i>s>i>a\n" | 
 |               + "3: x>s\n" | 
 |               + "4: c>d>i>s>i>a\n", | 
 |           "desugared-private-interface-methods", | 
 |           "0: s>i>a\n" | 
 |               + "1: d>i>s>i>a\n" | 
 |               + "2: l>i>s>i>a\n" | 
 |               + "3: x>s\n" | 
 |               + "4: c>d>i>s>i>a\n", | 
 |           "varhandle", | 
 |           "true\nfalse\n" | 
 |       ); | 
 |  | 
 |   @Rule | 
 |   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); | 
 |  | 
 |   @Rule | 
 |   public ExpectedException thrown = ExpectedException.none(); | 
 |  | 
 |   @Rule | 
 |   public TestDescriptionWatcher watcher = new TestDescriptionWatcher(); | 
 |  | 
 |   private boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) { | 
 |     DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion(); | 
 |     return failsOn.containsKey(vmVersion) | 
 |         && failsOn.get(vmVersion).contains(name); | 
 |   } | 
 |  | 
 |   private boolean expectedToFail(String name) { | 
 |     return failsOn(failsOn, name); | 
 |   } | 
 |  | 
 |   private boolean minSdkErrorExpected(String testName) { | 
 |     return minSdkErrorExpected.contains(testName); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void nativePrivateInterfaceMethods() throws Throwable { | 
 |     test("native-private-interface-methods", | 
 |         "privateinterfacemethods", "PrivateInterfaceMethods") | 
 |         .withMinApiLevel(AndroidApiLevel.N.getLevel()) | 
 |         .withKeepAll() | 
 |         .run(); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void desugaredPrivateInterfaceMethods() throws Throwable { | 
 |     assumeFalse("CF backend does not desugar", this instanceof R8CFRunExamplesJava9Test); | 
 |     final String iName = "privateinterfacemethods.I"; | 
 |     test("desugared-private-interface-methods", | 
 |         "privateinterfacemethods", "PrivateInterfaceMethods") | 
 |         .withMinApiLevel(AndroidApiLevel.M.getLevel()) | 
 |         .withKeepAll() | 
 |         .withDexCheck(dexInspector -> { | 
 |           ClassSubject companion = dexInspector.clazz( | 
 |               iName + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX); | 
 |           assertThat(companion, isPresent()); | 
 |           MethodSubject iFoo = companion.method( | 
 |               "java.lang.String", | 
 |               InterfaceMethodRewriter.PRIVATE_METHOD_PREFIX + "iFoo", | 
 |               ImmutableList.of(iName, "boolean")); | 
 |           assertThat(iFoo, isPresent()); | 
 |           assertTrue(iFoo.getMethod().isPublicMethod()); | 
 |         }) | 
 |         .run(); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void varHandle() throws Throwable { | 
 |     test("varhandle", "varhandle", "VarHandleTests") | 
 |         .withMinApiLevel(AndroidApiLevel.P.getLevel()) | 
 |         .withKeepAll() | 
 |         .run(); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void varHandleErrorDueToMinSdk() throws Throwable { | 
 |     test("varhandle-error-due-to-min-sdk", "varhandle", "VarHandleTests") | 
 |         .withMinApiLevel(AndroidApiLevel.O.getLevel()) | 
 |         .withKeepAll() | 
 |         .run(); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void testTwrCloseResourceMethod() throws Throwable { | 
 |     TestRunner<?> test = test("twr-close-resource", "twrcloseresource", "TwrCloseResourceTest"); | 
 |     test | 
 |         .withMinApiLevel(AndroidApiLevel.I.getLevel()) | 
 |         .withKeepAll() | 
 |         .withAndroidJar(AndroidApiLevel.K.getLevel()) | 
 |         .withArg(test.getInputJar().toAbsolutePath().toString()) | 
 |         .run(); | 
 |   } | 
 |  | 
 |   abstract RunExamplesJava9Test<B>.TestRunner<?> test(String testName, String packageName, | 
 |       String mainClass); | 
 |  | 
 |   void execute(String testName, | 
 |       String qualifiedMainClass, Path[] jars, Path[] dexes, List<String> args) throws IOException { | 
 |  | 
 |     boolean expectedToFail = expectedToFail(testName); | 
 |     if (expectedToFail) { | 
 |       thrown.expect(Throwable.class); | 
 |     } | 
 |     String output = ToolHelper.runArtNoVerificationErrors( | 
 |         Arrays.stream(dexes).map(Path::toString).collect(Collectors.toList()), | 
 |         qualifiedMainClass, | 
 |         builder -> { | 
 |           for (String arg : args) { | 
 |             builder.appendProgramArgument(arg); | 
 |           } | 
 |         }); | 
 |     String jvmResult = null; | 
 |     if (expectedJvmResult.containsKey(testName)) { | 
 |       jvmResult = expectedJvmResult.get(testName); | 
 |     } else if (!expectedToFail) { | 
 |       ToolHelper.ProcessResult javaResult = | 
 |           ToolHelper.runJava(ImmutableList.copyOf(jars), qualifiedMainClass); | 
 |       assertEquals("JVM run failed", javaResult.exitCode, 0); | 
 |       jvmResult = javaResult.stdout; | 
 |     } | 
 |  | 
 |     if (jvmResult != null) { | 
 |       assertTrue( | 
 |           "JVM output does not match art output.\n\tjvm: " | 
 |               + jvmResult | 
 |               + "\n\tart: " | 
 |               + output.replace("\r", ""), | 
 |           output.equals(jvmResult.replace("\r", ""))); | 
 |     } | 
 |   } | 
 |  | 
 | } |