Allow to add kotlin tests to the D8 test infrastructure

- Update kotlin stdlib to 1.2.0 to be align with the kotlin compiler used.

Change-Id: Ib40b7d0f8a4981afe9c4fd314c75aa288005dd81
diff --git a/build.gradle b/build.gradle
index 7672b72..d90da4c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -132,6 +132,12 @@
         }
         output.resourcesDir = 'build/classes/examples'
     }
+    examplesKotlin {
+        java {
+            srcDirs = ['src/test/examplesKotlin']
+        }
+        output.resourcesDir = 'build/classes/examplesKotlin'
+    }
     examplesAndroidN {
         java {
             srcDirs = ['src/test/examplesAndroidN']
@@ -218,7 +224,8 @@
     supportLibs 'junit:junit:4.12'
     supportLibs 'com.android.support.test.espresso:espresso-core:3.0.0'
     apiUsageSampleCompile sourceSets.main.output
-    debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.1.4-3'
+    debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0'
+    examplesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0'
     apt 'com.google.auto.value:auto-value:1.5'
 }
 
@@ -768,6 +775,18 @@
     dependsOn jar_debugTestResourcesKotlin
 }
 
+task buildExampleKotlinJars {
+    def kotlinSrcDir = file("src/test/examplesKotlin")
+    kotlinSrcDir.eachDir { dir ->
+        def name = dir.getName();
+        dependsOn "compile_example_kotlin_${name}"
+        task "compile_example_kotlin_${name}"(type: kotlin.Kotlinc) {
+            source = fileTree(dir: file("src/test/examplesKotlin/${name}"), include: '**/*.kt')
+            destination = file("build/test/examplesKotlin/${name}.jar")
+        }
+    }
+}
+
 // Proto lite generated code yields warnings when compiling with javac.
 // We change the options passed to javac to ignore it.
 compileExamplesJava.options.compilerArgs = ["-Xlint:none"]
@@ -1055,6 +1074,36 @@
     }
 }
 
+task buildExamplesKotlin {
+    if (OperatingSystem.current().isMacOsX() || OperatingSystem.current().isWindows()) {
+        logger.lifecycle("WARNING: Testing (including building kotlin examples) is only partially" +
+                " supported on your platform (" + OperatingSystem.current().getName() + ").")
+    } else if (!OperatingSystem.current().isLinux()) {
+        logger.lifecycle("WARNING: Testing (including building kotlin examples) is not supported " +
+                "on your platform. It is fully supported on Linux and partially supported on " +
+                "Mac OS and Windows")
+        return;
+    }
+    def examplesDir = file("src/test/examplesKotlin")
+    examplesDir.eachDir { dir ->
+        def name = dir.getName();
+        dependsOn "dex_example_kotlin_${name}"
+        def exampleOutputDir = file("build/test/examplesKotlin/" + name);
+        def dexPath = file("${exampleOutputDir}")
+        task "dex_example_kotlin_${name}"(type: dx.Dx,
+                dependsOn: "compile_example_kotlin_${name}") {
+            doFirst {
+                if (!dexPath.exists()) {
+                    dexPath.mkdirs()
+                }
+            }
+            source = files(tasks.getByPath("compile_example_kotlin_${name}")).asFileTree
+            destination = dexPath
+            debug = false
+        }
+    }
+}
+
 task buildExamples {
     if (OperatingSystem.current().isMacOsX() || OperatingSystem.current().isWindows()) {
         logger.lifecycle("WARNING: Testing (including building examples) is only partially supported on your " +
@@ -1280,6 +1329,7 @@
         }
         dependsOn downloadDeps
         dependsOn buildExamples
+        dependsOn buildExamplesKotlin
         dependsOn buildSmali
         dependsOn jctfCommonJar
         dependsOn jctfTestsClasses
diff --git a/src/test/examplesKotlin/loops/Loop.kt b/src/test/examplesKotlin/loops/Loop.kt
new file mode 100644
index 0000000..928dec8
--- /dev/null
+++ b/src/test/examplesKotlin/loops/Loop.kt
@@ -0,0 +1,14 @@
+// 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 loops
+
+fun loopOnArray(array: Array<Int>) {
+  for (i in array) {
+    println(i)
+  }
+}
+
+fun main(args: Array<String>) {
+  loopOnArray(arrayOf(1, 2, 3, 4, 5))
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
new file mode 100644
index 0000000..135101a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -0,0 +1,213 @@
+// Copyright (c) 2016, 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
+import com.android.tools.r8.R8RunArtTestsTest.DexTool;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.errors.Unreachable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public abstract class R8RunExamplesCommon {
+
+  protected enum Input {
+    DX, JAVAC, JAVAC_ALL, JAVAC_NONE
+  }
+
+  protected enum Output {
+    DEX,
+    CF
+  }
+
+  protected static String[] makeTest(
+      Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz) {
+    return makeTest(input, compiler, mode, clazz, Output.DEX);
+  }
+
+  protected static String[] makeTest(
+      Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz, Output output) {
+    String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
+    return new String[] {pkg, input.name(), compiler.name(), mode.name(), clazz, output.name()};
+  }
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  private final Input input;
+  private final CompilerUnderTest compiler;
+  private final CompilationMode mode;
+  private final String pkg;
+  private final String mainClass;
+  private final Output output;
+
+  public R8RunExamplesCommon(
+      String pkg,
+      String input,
+      String compiler,
+      String mode,
+      String mainClass,
+      String output) {
+    this.pkg = pkg;
+    this.input = Input.valueOf(input);
+    this.compiler = CompilerUnderTest.valueOf(compiler);
+    this.mode = CompilationMode.valueOf(mode);
+    this.mainClass = mainClass;
+    this.output = Output.valueOf(output);
+  }
+
+  private Path getOutputFile() {
+    return temp.getRoot().toPath().resolve("out.jar");
+  }
+
+  private Path getInputFile() {
+    switch(input) {
+      case DX:
+        return getOriginalDexFile();
+      case JAVAC:
+        return getOriginalJarFile("");
+      case JAVAC_ALL:
+        return getOriginalJarFile("_debuginfo_all");
+      case JAVAC_NONE:
+        return getOriginalJarFile("_debuginfo_none");
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  public Path getOriginalJarFile(String postFix) {
+    return Paths.get(getExampleDir(), pkg + postFix + JAR_EXTENSION);
+  }
+
+  private Path getOriginalDexFile() {
+    return Paths.get(getExampleDir(), pkg, ToolHelper.DEFAULT_DEX_FILENAME);
+  }
+
+  private DexTool getTool() {
+    return input == Input.DX ? DexTool.DX : DexTool.NONE;
+  }
+
+  private Path getOutputPath() {
+    return temp.getRoot().toPath().resolve("out.jar");
+  }
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Before
+  public void compile() throws Exception {
+    if (output == Output.CF && getFailingCompileCf().contains(mainClass)) {
+      thrown.expect(Throwable.class);
+    }
+    switch (compiler) {
+      case D8: {
+        assertTrue(output == Output.DEX);
+        ToolHelper.runD8(D8Command.builder()
+            .addProgramFiles(getInputFile())
+            .setOutputPath(getOutputFile())
+            .setMode(mode)
+            .build());
+        break;
+      }
+      case R8: {
+        ToolHelper.runR8(R8Command.builder()
+            .addProgramFiles(getInputFile())
+            .setOutputPath(output == Output.CF ? null : getOutputFile())
+            .setMode(mode)
+            .build(),
+            options -> {
+              if (output == Output.CF) {
+                options.programConsumer = new ClassFileConsumer.ArchiveConsumer(getOutputFile());
+              }
+            });
+        break;
+      }
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  @Test
+  public void outputIsIdentical() throws IOException, InterruptedException, ExecutionException {
+    if (!ToolHelper.artSupported()) {
+      return;
+    }
+
+    String original = getOriginalDexFile().toString();
+    Path generated = getOutputFile();
+
+    ToolHelper.ProcessResult javaResult = ToolHelper.runJava(getOriginalJarFile(""), mainClass);
+    if (javaResult.exitCode != 0) {
+      System.out.println(javaResult.stdout);
+      System.err.println(javaResult.stderr);
+      fail("JVM failed for: " + mainClass);
+    }
+
+    DexVm vm = ToolHelper.getDexVm();
+    TestCondition condition =
+        output == Output.CF ? getFailingRunCf().get(mainClass) : getFailingRun().get(mainClass);
+    if (condition != null && condition.test(getTool(), compiler, vm.getVersion(), mode)) {
+      thrown.expect(Throwable.class);
+    } else {
+      thrown = ExpectedException.none();
+    }
+
+    if (output == Output.CF) {
+      ToolHelper.ProcessResult result = ToolHelper.runJava(generated, mainClass);
+      if (result.exitCode != 0) {
+        System.err.println(result.stderr);
+        fail("JVM failed on compiled output for: " + mainClass);
+      }
+      if (!getFailingOutputCf().contains(mainClass)) {
+        assertEquals(
+            "JavaC/JVM and " + compiler.name() + "/JVM output differ",
+            javaResult.stdout,
+            result.stdout);
+      }
+      return;
+    }
+
+    // Check output against Art output on original dex file.
+    String output =
+        ToolHelper.checkArtOutputIdentical(original, generated.toString(), mainClass, vm);
+
+    // Check output against JVM output.
+    if (shouldMatchJVMOutput(vm.getVersion())) {
+      String javaOutput = javaResult.stdout;
+      assertEquals("JVM and Art output differ", javaOutput, output);
+    }
+  }
+
+  private boolean shouldMatchJVMOutput(DexVm.Version version) {
+    TestCondition condition = getOutputNotIdenticalToJVMOutput().get(mainClass);
+    return condition == null || !condition.test(getTool(), compiler, version, mode);
+  }
+
+  protected abstract String getExampleDir();
+
+  protected abstract Map<String, TestCondition> getFailingRun();
+
+  protected abstract Map<String, TestCondition> getFailingRunCf();
+
+  protected abstract Set<String> getFailingCompileCf();
+
+  protected abstract Set<String> getFailingOutputCf();
+
+  protected abstract Map<String, TestCondition> getOutputNotIdenticalToJVMOutput();
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
new file mode 100644
index 0000000..21f4f74
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
@@ -0,0 +1,97 @@
+// 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.TestCondition.R8_COMPILER;
+import static com.android.tools.r8.TestCondition.match;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
+import com.android.tools.r8.R8RunArtTestsTest.DexTool;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.Unreachable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class R8RunExamplesKotlinTest extends R8RunExamplesCommon {
+
+  @Parameters(name = "{0}_{1}_{2}_{3}_{5}")
+  public static Collection<String[]> data() {
+    String[] tests = {
+        "loops.LoopKt"
+    };
+
+    List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
+    for (String test : tests) {
+      fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
+      fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.RELEASE, test));
+      fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.DEBUG, test));
+      fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
+    }
+    return fullTestList;
+  }
+
+  @Override
+  protected String getExampleDir() {
+    return ToolHelper.EXAMPLES_KOTLIN_BUILD_DIR;
+  }
+
+  @Override
+  protected Map<String, TestCondition> getFailingRun() {
+    return Collections.emptyMap();
+  }
+
+  @Override
+  protected Map<String, TestCondition> getFailingRunCf() {
+    return Collections.emptyMap();
+  }
+
+  @Override
+  protected Set<String> getFailingCompileCf() {
+    return Collections.emptySet();
+  }
+
+  @Override
+  protected Set<String> getFailingOutputCf() {
+    return Collections.emptySet();
+  }
+
+  @Override
+  protected Map<String, TestCondition> getOutputNotIdenticalToJVMOutput() {
+    return Collections.emptyMap();
+  }
+
+  public R8RunExamplesKotlinTest(
+      String pkg,
+      String input,
+      String compiler,
+      String mode,
+      String mainClass,
+      String output) {
+    super(pkg, input, compiler, mode, mainClass, output);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index bb0c129..8fefe7e 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -36,59 +36,10 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class R8RunExamplesTest {
+public class R8RunExamplesTest extends R8RunExamplesCommon {
 
   private static final boolean ONLY_RUN_CF_TESTS = false;
 
-  enum Input {
-    DX, JAVAC, JAVAC_ALL, JAVAC_NONE
-  }
-
-  enum Output {
-    DEX,
-    CF
-  }
-
-  private static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_BUILD_DIR;
-
-  // Tests failing to run.
-  private static final Map<String, TestCondition> failingRun =
-      new ImmutableMap.Builder<String, TestCondition>()
-          .put("memberrebinding2.Test", match(R8_COMPILER)) // b/38187737
-          .build();
-
-  private static final Map<String, TestCondition> failingRunCf =
-      new ImmutableMap.Builder<String, TestCondition>()
-          .put("floating_point_annotations.FloatingPointValuedAnnotationTest", match(R8_COMPILER))
-          .build();
-
-  private static final Set<String> failingCompileCf =
-      new ImmutableSet.Builder<String>()
-          .add("invoke.Invoke") // outline / CF->IR
-          .add("trycatch.TryCatch") // inline / CF->IR
-          .build();
-
-  private static final Set<String> failingOutputCf =
-      new ImmutableSet.Builder<String>()
-          .add("regress_62300145.Regress") // annotations
-          .add("throwing.Throwing") // no line info
-          .build();
-
-  private static final Map<String, TestCondition> outputNotIdenticalToJVMOutput =
-      new ImmutableMap.Builder<String, TestCondition>()
-          // Traverses stack frames that contain Art specific frames.
-          .put("throwing.Throwing", TestCondition.any())
-          // DEX enclosing-class annotations don't distinguish member classes from local classes.
-          // This results in Class.isLocalClass always being false and Class.isMemberClass always
-          // being true even when the converse is the case when running on the JVM.
-          .put("enclosingmethod.Main", TestCondition.any())
-          // Early art versions incorrectly print Float.MIN_VALUE.
-          .put(
-              "filledarray.FilledArray",
-              TestCondition.match(
-                  TestCondition.runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
-          .build();
-
   @Parameters(name = "{0}_{1}_{2}_{3}_{5}")
   public static Collection<String[]> data() {
     String[] tests = {
@@ -164,27 +115,6 @@
     return fullTestList;
   }
 
-  private static String[] makeTest(
-      Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz) {
-    return makeTest(input, compiler, mode, clazz, Output.DEX);
-  }
-
-  private static String[] makeTest(
-      Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz, Output output) {
-    String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
-    return new String[] {pkg, input.name(), compiler.name(), mode.name(), clazz, output.name()};
-  }
-
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
-  private final Input input;
-  private final CompilerUnderTest compiler;
-  private final CompilationMode mode;
-  private final String pkg;
-  private final String mainClass;
-  private final Output output;
-
   public R8RunExamplesTest(
       String pkg,
       String input,
@@ -192,138 +122,58 @@
       String mode,
       String mainClass,
       String output) {
-    this.pkg = pkg;
-    this.input = Input.valueOf(input);
-    this.compiler = CompilerUnderTest.valueOf(compiler);
-    this.mode = CompilationMode.valueOf(mode);
-    this.mainClass = mainClass;
-    this.output = Output.valueOf(output);
+    super(pkg, input, compiler, mode, mainClass, output);
   }
 
-  private Path getOutputFile() {
-    return temp.getRoot().toPath().resolve("out.jar");
+  @Override
+  protected String getExampleDir() {
+    return ToolHelper.EXAMPLES_BUILD_DIR;
   }
 
-  private Path getInputFile() {
-    switch(input) {
-      case DX:
-        return getOriginalDexFile();
-      case JAVAC:
-        return getOriginalJarFile("");
-      case JAVAC_ALL:
-        return getOriginalJarFile("_debuginfo_all");
-      case JAVAC_NONE:
-        return getOriginalJarFile("_debuginfo_none");
-      default:
-        throw new Unreachable();
-    }
+  @Override
+  protected Map<String, TestCondition> getFailingRun() {
+    return new ImmutableMap.Builder<String, TestCondition>()
+        .put("memberrebinding2.Test", match(R8_COMPILER)) // b/38187737
+        .build();
   }
 
-  public Path getOriginalJarFile(String postFix) {
-    return Paths.get(EXAMPLE_DIR, pkg + postFix + JAR_EXTENSION);
+  @Override
+  protected Map<String, TestCondition> getFailingRunCf() {
+    return new ImmutableMap.Builder<String, TestCondition>()
+        .put("floating_point_annotations.FloatingPointValuedAnnotationTest", match(R8_COMPILER))
+        .build();
   }
 
-  private Path getOriginalDexFile() {
-    return Paths.get(EXAMPLE_DIR, pkg, ToolHelper.DEFAULT_DEX_FILENAME);
+  @Override
+  protected Set<String> getFailingCompileCf() {
+    return new ImmutableSet.Builder<String>()
+        .add("invoke.Invoke") // outline / CF->IR
+        .add("trycatch.TryCatch") // inline / CF->IR
+        .build();
   }
 
-  private DexTool getTool() {
-    return input == Input.DX ? DexTool.DX : DexTool.NONE;
+  @Override
+  protected Set<String> getFailingOutputCf() {
+    return new ImmutableSet.Builder<String>()
+        .add("regress_62300145.Regress") // annotations
+        .add("throwing.Throwing") // no line info
+        .build();
   }
 
-  private Path getOutputPath() {
-    return temp.getRoot().toPath().resolve("out.jar");
-  }
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Before
-  public void compile() throws Exception {
-    if (output == Output.CF && failingCompileCf.contains(mainClass)) {
-      thrown.expect(Throwable.class);
-    }
-    switch (compiler) {
-      case D8: {
-        assertTrue(output == Output.DEX);
-        ToolHelper.runD8(D8Command.builder()
-            .addProgramFiles(getInputFile())
-            .setOutputPath(getOutputFile())
-            .setMode(mode)
-            .build());
-        break;
-      }
-      case R8: {
-        ToolHelper.runR8(R8Command.builder()
-            .addProgramFiles(getInputFile())
-            .setOutputPath(getOutputFile())
-            .setMode(mode)
-            .build(),
-            options -> {
-              if (output == Output.CF) {
-                options.programConsumer = new ClassFileConsumer.ArchiveConsumer(getOutputFile());
-              }
-            });
-        break;
-      }
-      default:
-        throw new Unreachable();
-    }
-  }
-
-  @Test
-  public void outputIsIdentical() throws IOException, InterruptedException, ExecutionException {
-    if (!ToolHelper.artSupported()) {
-      return;
-    }
-
-    String original = getOriginalDexFile().toString();
-    Path generated = getOutputFile();
-
-    ToolHelper.ProcessResult javaResult = ToolHelper.runJava(getOriginalJarFile(""), mainClass);
-    if (javaResult.exitCode != 0) {
-      System.out.println(javaResult.stdout);
-      System.err.println(javaResult.stderr);
-      fail("JVM failed for: " + mainClass);
-    }
-
-    DexVm vm = ToolHelper.getDexVm();
-    TestCondition condition =
-        output == Output.CF ? failingRunCf.get(mainClass) : failingRun.get(mainClass);
-    if (condition != null && condition.test(getTool(), compiler, vm.getVersion(), mode)) {
-      thrown.expect(Throwable.class);
-    } else {
-      thrown = ExpectedException.none();
-    }
-
-    if (output == Output.CF) {
-      ToolHelper.ProcessResult result = ToolHelper.runJava(generated, mainClass);
-      if (result.exitCode != 0) {
-        System.err.println(result.stderr);
-        fail("JVM failed on compiled output for: " + mainClass);
-      }
-      if (!failingOutputCf.contains(mainClass)) {
-        assertEquals(
-            "JavaC/JVM and " + compiler.name() + "/JVM output differ",
-            javaResult.stdout,
-            result.stdout);
-      }
-      return;
-    }
-
-    // Check output against Art output on original dex file.
-    String output =
-        ToolHelper.checkArtOutputIdentical(original, generated.toString(), mainClass, vm);
-
-    // Check output against JVM output.
-    if (shouldMatchJVMOutput(vm.getVersion())) {
-      String javaOutput = javaResult.stdout;
-      assertEquals("JVM and Art output differ", javaOutput, output);
-    }
-  }
-
-  private boolean shouldMatchJVMOutput(DexVm.Version version) {
-    TestCondition condition = outputNotIdenticalToJVMOutput.get(mainClass);
-    return condition == null || !condition.test(getTool(), compiler, version, mode);
+  @Override
+  protected Map<String, TestCondition> getOutputNotIdenticalToJVMOutput() {
+    return new ImmutableMap.Builder<String, TestCondition>()
+        // Traverses stack frames that contain Art specific frames.
+        .put("throwing.Throwing", TestCondition.any())
+        // DEX enclosing-class annotations don't distinguish member classes from local classes.
+        // This results in Class.isLocalClass always being false and Class.isMemberClass always
+        // being true even when the converse is the case when running on the JVM.
+        .put("enclosingmethod.Main", TestCondition.any())
+        // Early art versions incorrectly print Float.MIN_VALUE.
+        .put(
+            "filledarray.FilledArray",
+            TestCondition.match(
+                TestCondition.runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
+        .build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9c88029..70b8f72 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -65,6 +65,7 @@
   public static final String EXAMPLES_ANDROID_P_DIR = TESTS_DIR + "examplesAndroidP/";
   public static final String TESTS_BUILD_DIR = BUILD_DIR + "test/";
   public static final String EXAMPLES_BUILD_DIR = TESTS_BUILD_DIR + "examples/";
+  public static final String EXAMPLES_KOTLIN_BUILD_DIR = TESTS_BUILD_DIR + "examplesKotlin/";
   public static final String EXAMPLES_ANDROID_N_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidN/";
   public static final String EXAMPLES_ANDROID_O_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidO/";
   public static final String EXAMPLES_ANDROID_P_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidP/";