[Retrace] Build an external retrace library and run tests on it

Change-Id: Icf95d17ac0410597e60631f8c5f9bd4d57bbfb78
diff --git a/build.gradle b/build.gradle
index be64cba..33dfc26 100644
--- a/build.gradle
+++ b/build.gradle
@@ -293,6 +293,7 @@
 def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
 def r8LibTestPath = "$buildDir/classes/r8libtest"
 def java11ClassFiles = "$buildDir/classes/java/mainJava11"
+def r8RetracePath = "$buildDir/libs/r8retrace.jar"
 
 def osString = OperatingSystem.current().isLinux() ? "linux" :
         OperatingSystem.current().isMacOsX() ? "mac" : "windows"
@@ -1070,6 +1071,17 @@
     outputs.file r8DesugaredPath
 }
 
+task R8Retrace {
+    dependsOn R8Lib
+    dependsOn r8LibCreateTask(
+            "Retrace",
+            ["src/main/keep_retrace.txt"],
+            R8Lib,
+            r8RetracePath,
+    ).dependsOn(R8Lib)
+    outputs.file r8RetracePath
+}
+
 task sourceJar(type: Jar, dependsOn: classes) {
     classifier = 'src'
     from sourceSets.main.allSource
diff --git a/src/main/keep_retrace.txt b/src/main/keep_retrace.txt
new file mode 100644
index 0000000..6a71f9e
--- /dev/null
+++ b/src/main/keep_retrace.txt
@@ -0,0 +1,15 @@
+# Copyright (c) 2021, 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.
+
+# The retrace api is separated out without repackaging which is why this broad
+# rule is used.
+-keep public class com.android.tools.r8.retrace.* {
+     public <methods>;
+     public <fields>;
+ }
+-keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
+-keepparameternames
+# This is run on r8lib so keep everything in lib that is traced. That way
+# we only need a single mapping file
+-keep,allowshrinking class * { *; }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index ad512cd..a85ae6a 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -176,6 +176,7 @@
   public static final Path R8LIB_EXCLUDE_DEPS_MAP =
       Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar.map");
   public static final Path DEPS = Paths.get(LIBS_DIR, "deps_all.jar");
+  public static final Path R8_RETRACE_JAR = Paths.get(LIBS_DIR, "r8retrace.jar");
 
   public static final Path DESUGAR_LIB_CONVERSIONS =
       Paths.get(LIBS_DIR, "library_desugar_conversions.zip");
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 2074afa..cd839f1 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTraceWithInfo;
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.PGStackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Charsets;
 import java.io.ByteArrayOutputStream;
@@ -36,16 +38,30 @@
 import org.junit.Rule;
 import org.junit.Test;
 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 RetraceCommandLineTests {
 
-  private static final boolean testExternal = false;
+  private static String SMILEY_EMOJI = "\uD83D\uDE00";
+
   private static final String WAITING_MESSAGE =
       "Waiting for stack-trace input..." + StringUtils.LINE_SEPARATOR;
 
   @Rule public TemporaryFolder folder = new TemporaryFolder();
 
-  private static String SMILEY_EMOJI = "\uD83D\uDE00";
+  private final boolean testExternal;
+
+  @Parameters(name = "{0}")
+  public static Boolean[] data() {
+    return BooleanUtils.values();
+  }
+
+  public RetraceCommandLineTests(boolean testExternal) {
+    this.testExternal = testExternal;
+  }
 
   @Test
   public void testPrintIdentityStackTraceFile() throws IOException {
@@ -233,11 +249,14 @@
   private ProcessResult runRetraceCommandLine(File stdInput, Collection<String> args)
       throws IOException {
     if (testExternal) {
+      // The external dependency is built on top of R8Lib. If test.py is run with
+      // no r8lib, do not try and run the external R8 Retrace since it has not been built.
+      assumeTrue(Files.exists(ToolHelper.R8LIB_JAR));
       List<String> command = new ArrayList<>();
       command.add(ToolHelper.getSystemJavaExecutable());
       command.add("-ea");
       command.add("-cp");
-      command.add(ToolHelper.R8_JAR.toString());
+      command.add(ToolHelper.R8_RETRACE_JAR.toString());
       command.add("com.android.tools.r8.retrace.Retrace");
       command.addAll(args);
       ProcessBuilder builder = new ProcessBuilder(command);
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 34d4b0f..64979ea 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -10,10 +10,13 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
 import com.android.tools.r8.retrace.stacktraces.ActualBotStackTraceBase;
 import com.android.tools.r8.retrace.stacktraces.ActualIdentityStackTrace;
@@ -46,8 +49,12 @@
 import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
 import com.android.tools.r8.retrace.stacktraces.UnicodeInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.UnknownSourceStackTrace;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
@@ -60,44 +67,50 @@
 @RunWith(Parameterized.class)
 public class RetraceTests extends TestBase {
 
-  @Parameters(name = "{0}, use regular expression: {1}")
+  @Parameters(name = "{0}, use regular expression: {1}, external: {2}")
   public static Collection<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
+    TestParameters noneRuntime = getTestParameters().withNoneRuntime().build().iterator().next();
+    return ImmutableList.of(
+        new Object[] {noneRuntime, false, false},
+        new Object[] {noneRuntime, true, false},
+        new Object[] {noneRuntime, true, true});
   }
 
   private final boolean useRegExpParsing;
+  private final boolean external;
 
-  public RetraceTests(TestParameters parameters, boolean useRegExpParsing) {
+  public RetraceTests(TestParameters parameters, boolean useRegExpParsing, boolean external) {
     this.useRegExpParsing = useRegExpParsing;
+    this.external = external;
   }
 
   @Test
-  public void testCanMapExceptionClass() {
+  public void testCanMapExceptionClass() throws Exception {
     runRetraceTest(new ObfucatedExceptionClassStackTrace());
   }
 
   @Test
-  public void testSuppressedStackTrace() {
+  public void testSuppressedStackTrace() throws Exception {
     runRetraceTest(new SuppressedStackTrace());
   }
 
   @Test
-  public void testFileNameStackTrace() {
+  public void testFileNameStackTrace() throws Exception {
     runRetraceTest(new FileNameExtensionStackTrace());
   }
 
   @Test
-  public void testInlineFileNameStackTrace() {
+  public void testInlineFileNameStackTrace() throws Exception {
     runRetraceTest(new InlineFileNameStackTrace());
   }
 
   @Test
-  public void testInlineFileNameWithInnerClassesStackTrace() {
+  public void testInlineFileNameWithInnerClassesStackTrace() throws Exception {
     runRetraceTest(new InlineFileNameWithInnerClassesStackTrace());
   }
 
   @Test
-  public void testNoObfuscationRangeMappingWithStackTrace() {
+  public void testNoObfuscationRangeMappingWithStackTrace() throws Exception {
     runRetraceTest(new NoObfuscationRangeMappingWithStackTrace());
   }
 
@@ -124,18 +137,18 @@
   }
 
   @Test
-  public void testInvalidStackTraceLineWarnings() {
+  public void testInvalidStackTraceLineWarnings() throws Exception {
     InvalidStackTrace invalidStackTraceTest = new InvalidStackTrace();
     runRetraceTest(invalidStackTraceTest).assertNoMessages();
   }
 
   @Test
-  public void testAssertionErrorInRetrace() {
+  public void testAssertionErrorInRetrace() throws Exception {
     runRetraceTest(new RetraceAssertionErrorStackTrace());
   }
 
   @Test
-  public void testActualStackTraces() {
+  public void testActualStackTraces() throws Exception {
     List<ActualBotStackTraceBase> stackTraces =
         ImmutableList.of(new ActualIdentityStackTrace(), new ActualRetraceBotStackTrace());
     for (ActualBotStackTraceBase stackTrace : stackTraces) {
@@ -145,37 +158,37 @@
   }
 
   @Test
-  public void testAmbiguousStackTrace() {
+  public void testAmbiguousStackTrace() throws Exception {
     runRetraceTest(new AmbiguousStackTrace());
   }
 
   @Test
-  public void testAmbiguousMissingLineStackTrace() {
+  public void testAmbiguousMissingLineStackTrace() throws Exception {
     runRetraceTest(new AmbiguousMissingLineStackTrace());
   }
 
   @Test
-  public void testAmbiguousMissingLineNotVerbose() {
+  public void testAmbiguousMissingLineNotVerbose() throws Exception {
     runRetraceTest(new AmbiguousWithSignatureNonVerboseStackTrace());
   }
 
   @Test
-  public void testAmbiguousMultipleMappingsTest() {
+  public void testAmbiguousMultipleMappingsTest() throws Exception {
     runRetraceTest(new AmbiguousWithMultipleLineMappingsStackTrace());
   }
 
   @Test
-  public void testInliningWithLineNumbers() {
+  public void testInliningWithLineNumbers() throws Exception {
     runRetraceTest(new InlineWithLineNumbersStackTrace());
   }
 
   @Test
-  public void testInliningNoLineNumberInfoStackTraces() {
+  public void testInliningNoLineNumberInfoStackTraces() throws Exception {
     runRetraceTest(new InlineNoLineNumberStackTrace());
   }
 
   @Test
-  public void testCircularReferenceStackTrace() {
+  public void testCircularReferenceStackTrace() throws Exception {
     // Proguard retrace (and therefore the default regular expression) will not retrace circular
     // reference exceptions.
     assumeFalse(useRegExpParsing);
@@ -183,61 +196,61 @@
   }
 
   @Test
-  public void testObfuscatedRangeToSingleLine() {
+  public void testObfuscatedRangeToSingleLine() throws Exception {
     runRetraceTest(new ObfuscatedRangeToSingleLineStackTrace());
   }
 
   @Test
   @Ignore("b/170293908")
-  public void testBootLoaderAndNamedModulesStackTrace() {
+  public void testBootLoaderAndNamedModulesStackTrace() throws Exception {
     assumeFalse(useRegExpParsing);
     runRetraceTest(new NamedModuleStackTrace());
   }
 
   @Test
-  public void testUnknownSourceStackTrace() {
+  public void testUnknownSourceStackTrace() throws Exception {
     runRetraceTest(new UnknownSourceStackTrace());
   }
 
   @Test
-  public void testInlineSourceFileContext() {
+  public void testInlineSourceFileContext() throws Exception {
     runRetraceTest(new InlineSourceFileContextStackTrace());
   }
 
   @Test
-  public void testColonInSourceFileNameStackTrace() {
+  public void testColonInSourceFileNameStackTrace() throws Exception {
     runRetraceTest(new ColonInFileNameStackTrace());
   }
 
   @Test
-  public void testMultipleDotsInFileNameStackTrace() {
+  public void testMultipleDotsInFileNameStackTrace() throws Exception {
     runRetraceTest(new MultipleDotsInFileNameStackTrace());
   }
 
   @Test
-  public void testUnicodeInFileNameStackTrace() {
+  public void testUnicodeInFileNameStackTrace() throws Exception {
     runRetraceTest(new UnicodeInFileNameStackTrace());
   }
 
   @Test
-  public void testMemberFieldOverlapStackTrace() {
+  public void testMemberFieldOverlapStackTrace() throws Exception {
     MemberFieldOverlapStackTrace stackTraceForTest = new MemberFieldOverlapStackTrace();
     runRetraceTest(stackTraceForTest);
     inspectRetraceTest(stackTraceForTest, stackTraceForTest::inspectField);
   }
 
   @Test
-  public void testSourceFileWithNumberAndEmptyStackTrace() {
+  public void testSourceFileWithNumberAndEmptyStackTrace() throws Exception {
     runRetraceTest(new SourceFileWithNumberAndEmptyStackTrace());
   }
 
   @Test
-  public void testSourceFileNameSynthesizeStackTrace() {
+  public void testSourceFileNameSynthesizeStackTrace() throws Exception {
     runRetraceTest(new SourceFileNameSynthesizeStackTrace());
   }
 
   @Test
-  public void testAutoStackTrace() {
+  public void testAutoStackTrace() throws Exception {
     runRetraceTest(new AutoStackTrace());
   }
 
@@ -247,17 +260,50 @@
         Retracer.createDefault(stackTraceForTest::mapping, new TestDiagnosticMessagesImpl()));
   }
 
-  private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
-    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
-    RetraceCommand retraceCommand =
-        RetraceCommand.builder(diagnosticsHandler)
-            .setProguardMapProducer(stackTraceForTest::mapping)
-            .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
-            .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
-            .setRetracedStackTraceConsumer(
-                retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
-            .build();
-    Retrace.run(retraceCommand);
-    return diagnosticsHandler;
+  private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest)
+      throws Exception {
+    if (external) {
+      // The external dependency is built on top of R8Lib. If test.py is run with
+      // no r8lib, do not try and run the external R8 Retrace since it has not been built.
+      assumeTrue(Files.exists(ToolHelper.R8LIB_JAR));
+      Path path = temp.newFolder().toPath();
+      Path mappingFile = path.resolve("mapping");
+      Files.write(mappingFile, stackTraceForTest.mapping().getBytes());
+      Path stackTraceFile = path.resolve("stacktrace.txt");
+      Files.write(
+          stackTraceFile,
+          StringUtils.joinLines(stackTraceForTest.obfuscatedStackTrace())
+              .getBytes(StandardCharsets.UTF_8));
+
+      List<String> command = new ArrayList<>();
+      command.add(ToolHelper.getSystemJavaExecutable());
+      command.add("-ea");
+      command.add("-cp");
+      command.add(ToolHelper.R8_RETRACE_JAR.toString());
+      command.add("com.android.tools.r8.retrace.Retrace");
+      command.add(mappingFile.toString());
+      command.add(stackTraceFile.toString());
+      command.add("-quiet");
+      ProcessBuilder builder = new ProcessBuilder(command);
+      ProcessResult processResult = ToolHelper.runProcess(builder);
+      assertEquals(
+          StringUtils.joinLines(stackTraceForTest.retracedStackTrace())
+              + StringUtils.LINE_SEPARATOR,
+          processResult.stdout);
+      // TODO(b/177204438): Parse diagnostics from stdErr
+      return new TestDiagnosticMessagesImpl();
+    } else {
+      TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+      RetraceCommand retraceCommand =
+          RetraceCommand.builder(diagnosticsHandler)
+              .setProguardMapProducer(stackTraceForTest::mapping)
+              .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+              .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
+              .setRetracedStackTraceConsumer(
+                  retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
+              .build();
+      Retrace.run(retraceCommand);
+      return diagnosticsHandler;
+    }
   }
 }
diff --git a/tools/test.py b/tools/test.py
index cf14685..dffc4565 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -253,6 +253,7 @@
     # Force gradle to build a version of r8lib without dependencies for
     # BootstrapCurrentEqualityTest.
     gradle_args.append('R8LibNoDeps')
+    gradle_args.append('R8Retrace')
   if options.r8lib_no_deps:
     gradle_args.append('-Pr8lib_no_deps')
   if options.worktree: