Merge "Add a smoke test that runs r8.jar in a forked process   so that the built jar is loaded separately and tested."
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 5200a09..7b4f199 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -56,6 +56,8 @@
 
   // Actually running Proguard should only be during development.
   private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
+  // Actually running r8.jar in a forked process.
+  private static final boolean RUN_R8_JAR = System.getProperty("run_r8_jar") != null;
 
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -71,6 +73,13 @@
   }
 
   /**
+   * Check if tests should run R8 in a forked process when applicable.
+   */
+  protected boolean isRunR8Jar() {
+    return RUN_R8_JAR;
+  }
+
+  /**
    * Write lines of text to a temporary file.
    */
   protected Path writeTextToTempFile(String... lines) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 10f62a1..200a1e7 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -70,6 +70,7 @@
 public class ToolHelper {
 
   public static final String BUILD_DIR = "build/";
+  public static final String LIBS_DIR = BUILD_DIR + "libs/";
   public static final String TESTS_DIR = "src/test/";
   public static final String EXAMPLES_DIR = TESTS_DIR + "examples/";
   public static final String EXAMPLES_ANDROID_O_DIR = TESTS_DIR + "examplesAndroidO/";
@@ -78,6 +79,8 @@
   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_KOTLIN_RESOURCE_DIR =
+      TESTS_BUILD_DIR + "kotlinR8TestResources/";
   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/";
@@ -979,6 +982,12 @@
     return forkJava(dir, R8.class, args);
   }
 
+  public static ProcessResult forkR8Jar(Path dir, String... args)
+      throws IOException, InterruptedException {
+    String r8Jar = Paths.get(LIBS_DIR,  "r8.jar").toAbsolutePath().toString();
+    return forkJavaWithJar(dir, r8Jar, Arrays.asList(args));
+  }
+
   public static ProcessResult forkGenerateMainDexList(Path dir, List<String> args1, String... args2)
       throws IOException, InterruptedException {
     List<String> args = new ArrayList<>();
@@ -997,8 +1006,18 @@
     return forkJava(dir, clazz, Arrays.asList(args));
   }
 
+  private static ProcessResult forkJavaWithJar(Path dir, String jarPath, List<String> args)
+      throws IOException {
+    List<String> command = new ImmutableList.Builder<String>()
+        .add(getJavaExecutable())
+        .add("-jar").add(jarPath)
+        .addAll(args)
+        .build();
+    return runProcess(new ProcessBuilder(command).directory(dir.toFile()));
+  }
+
   private static ProcessResult forkJava(Path dir, Class clazz, List<String> args)
-      throws IOException, InterruptedException {
+      throws IOException {
     List<String> command = new ImmutableList.Builder<String>()
         .add(getJavaExecutable())
         .add("-cp").add(System.getProperty("java.class.path"))
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index e1431a6..062095c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -43,7 +43,7 @@
     abstract boolean match(DexClass clazz);
   }
 
-  private static class Group extends LambdaOrGroup {
+  static class Group extends LambdaOrGroup {
     final String pkg;
     final String capture;
     final int arity;
@@ -83,31 +83,32 @@
     }
   }
 
-  private Group kstyleImpl(String pkg, String capture, int arity, int singletons) {
+  private static Group kstyleImpl(String pkg, String capture, int arity, int singletons) {
     assertEquals(capture.isEmpty(), singletons != 0);
     return new Group(pkg, capture, arity, KOTLIN_FUNCTION_IFACE_STR + arity, singletons);
   }
 
-  private Group kstyle(String pkg, int arity, int singletons) {
+  static Group kstyle(String pkg, int arity, int singletons) {
     assertTrue(singletons != 0);
     return kstyleImpl(pkg, "", arity, singletons);
   }
 
-  private Group kstyle(String pkg, String capture, int arity) {
+  private static Group kstyle(String pkg, String capture, int arity) {
     assertFalse(capture.isEmpty());
     return kstyleImpl(pkg, capture, arity, 0);
   }
 
-  private Group jstyleImpl(String pkg, String capture, int arity, String sam, int singletons) {
+  private static Group jstyleImpl(
+      String pkg, String capture, int arity, String sam, int singletons) {
     assertTrue(capture.isEmpty() || singletons == 0);
     return new Group(pkg, capture, arity, sam, singletons);
   }
 
-  private Group jstyle(String pkg, String capture, int arity, String sam) {
+  private static Group jstyle(String pkg, String capture, int arity, String sam) {
     return jstyleImpl(pkg, capture, arity, sam, 0);
   }
 
-  private Group jstyle(String pkg, int arity, String sam, int singletons) {
+  private static Group jstyle(String pkg, int arity, String sam, int singletons) {
     return jstyleImpl(pkg, "", arity, sam, singletons);
   }
 
@@ -116,7 +117,7 @@
     final String name;
     final int arity;
 
-    private Lambda(String pkg, String name, int arity) {
+    Lambda(String pkg, String name, int arity) {
       this.pkg = pkg;
       this.name = name;
       this.arity = arity;
@@ -143,7 +144,15 @@
     final List<DexClass> groups = new ArrayList<>();
 
     Verifier(AndroidApp app) throws IOException, ExecutionException {
-      this.dexInspector = new DexInspector(app);
+      this(new DexInspector(app));
+    }
+
+    Verifier(DexInspector dexInspector) {
+      this.dexInspector = dexInspector;
+      initGroupsAndLambdas();
+    }
+
+    private void initGroupsAndLambdas() {
       dexInspector.forAllClasses(clazz -> {
         DexClass dexClass = clazz.getDexClass();
         if (isLambdaOrGroup(dexClass)) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
new file mode 100644
index 0000000..2beb777
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, 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.kotlin;
+
+import static com.android.tools.r8.ToolHelper.EXAMPLES_KOTLIN_RESOURCE_DIR;
+import static com.android.tools.r8.kotlin.KotlinLambdaMergingTest.kstyle;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Group;
+import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Lambda;
+import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Verifier;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+
+public class KotlinxMetadataExtensionsServiceTest extends TestBase {
+
+  private void forkR8_kstyle_trivial(boolean allowAccessModification) throws Exception {
+    if  (!isRunR8Jar()) {
+      return;
+    }
+    Path working = temp.getRoot().toPath();
+    Path kotlinJar =
+        Paths.get(EXAMPLES_KOTLIN_RESOURCE_DIR, "JAVA_8", "lambdas_kstyle_trivial.jar")
+            .toAbsolutePath();
+    Path output = working.resolve("classes.dex");
+    assertFalse(Files.exists(output));
+    Path proguardConfiguration = temp.newFile("test.conf").toPath();
+    List<String> lines = ImmutableList.of(
+        "-keepattributes Signature,InnerClasses,EnclosingMethod",
+        "-keep class **MainKt {",
+        "  public static void main(...);",
+        "}",
+        "-printmapping",
+        "-dontobfuscate",
+        allowAccessModification ? "-allowaccessmodification" : ""
+    );
+    FileUtils.writeTextFile(proguardConfiguration, lines);
+    ProcessResult result = ToolHelper.forkR8Jar(working,
+        "--pg-conf", proguardConfiguration.toString(),
+        "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.O).toAbsolutePath().toString(),
+        kotlinJar.toString());
+    assertEquals(0, result.exitCode);
+    assertThat(result.stderr, not(containsString(
+        "No MetadataExtensions instances found in the classpath")));
+    assertTrue(Files.exists(output));
+
+    DexInspector inspector = new DexInspector(output);
+    Verifier verifier = new Verifier(inspector);
+    String pkg = "lambdas_kstyle_trivial";
+    verifier.assertLambdaGroups(
+        allowAccessModification ?
+            new Group[]{
+                kstyle("", 0, 4),
+                kstyle("", 1, 8),
+                kstyle("", 2, 2), // -\
+                kstyle("", 2, 5), // - 3 groups different by main method
+                kstyle("", 2, 4), // -/
+                kstyle("", 3, 2),
+                kstyle("", 22, 2)} :
+            new Group[]{
+                kstyle(pkg, 0, 2),
+                kstyle(pkg, 1, 4),
+                kstyle(pkg, 2, 5), // - 2 groups different by main method
+                kstyle(pkg, 2, 4), // -/
+                kstyle(pkg, 3, 2),
+                kstyle(pkg, 22, 2),
+                kstyle(pkg + "/inner", 0, 2),
+                kstyle(pkg + "/inner", 1, 4)}
+    );
+
+    verifier.assertLambdas(
+        allowAccessModification ?
+            new Lambda[]{
+                new Lambda(pkg, "MainKt$testStateless$6", 1) /* Banned for limited inlining */} :
+            new Lambda[]{
+                new Lambda(pkg, "MainKt$testStateless$6", 1), /* Banned for limited inlining */
+                new Lambda(pkg, "MainKt$testStateless$8", 2),
+                new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)}
+    );
+  }
+
+  @Test
+  public void testTrivialKs_allowAccessModification() throws Exception {
+    forkR8_kstyle_trivial(true);
+  }
+
+  @Test
+  public void testTrivialKs_notAllowAccessModification() throws Exception {
+    forkR8_kstyle_trivial(false);
+  }
+
+}