Enable --regex, --verbose and --info as command line parameters

This makes retrace compatible with the command line interface exposed by proguard.

Bug: 132850880
Change-Id: I835074dc62b431b7bfb85fb6c8204f0e57a38626
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index c6dad1b..057875d 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.ExceptionUtils.STATUS_ERROR;
 
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -35,7 +36,7 @@
 
   public static final String USAGE_MESSAGE =
       StringUtils.lines(
-          "Usage: retrace <proguard-map> <stacktrace-file>",
+          "Usage: retrace <proguard-map> <stacktrace-file> [--regex <regexp>, --verbose, --info]",
           "  where <proguard-map> is an r8 generated mapping file.");
 
   private static Builder parseArguments(String[] args, DiagnosticsHandler diagnosticsHandler) {
@@ -48,16 +49,19 @@
       if (help != null) {
         return null;
       }
+      Boolean info = OptionsParsing.tryParseBoolean(context, "--info");
+      if (info != null) {
+        // This is already set in the diagnostics handler.
+        continue;
+      }
       Boolean verbose = OptionsParsing.tryParseBoolean(context, "--verbose");
       if (verbose != null) {
-        // TODO(b/132850880): Enable support for verbose.
-        diagnosticsHandler.error(new StringDiagnostic("Currently no support for --verbose"));
+        builder.setVerbose(true);
         continue;
       }
       String regex = OptionsParsing.tryParseSingle(context, "--regex", "r");
       if (regex != null && !regex.isEmpty()) {
-        // TODO(b/132850880): Enable support for regex.
-        diagnosticsHandler.error(new StringDiagnostic("Currently no support for --regex"));
+        builder.setRegularExpression(regex);
         continue;
       }
       if (!hasSetProguardMap) {
@@ -108,7 +112,7 @@
   }
 
   /**
-   * The main entry point for running the retrace.
+   * The main entry point for running retrace.
    *
    * @param command The command that describes the desired behavior of this retrace invocation.
    */
@@ -140,9 +144,11 @@
     }
   }
 
-  static void run(String[] args) {
-    DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
-    Builder builder = parseArguments(args, diagnosticsHandler);
+  public static void run(String[] args) {
+    RetraceDiagnosticsHandler retraceDiagnosticsHandler =
+        new RetraceDiagnosticsHandler(
+            new DiagnosticsHandler() {}, Arrays.asList(args).contains("--info"));
+    Builder builder = parseArguments(args, retraceDiagnosticsHandler);
     if (builder == null) {
       // --help was an argument to list
       assert Arrays.asList(args).contains("--help");
@@ -153,6 +159,7 @@
         retraced -> System.out.print(StringUtils.lines(retraced)));
     run(builder.build());
   }
+
   /**
    * The main entry point for running a legacy compatible retrace from the command line.
    *
@@ -190,4 +197,33 @@
       System.exit(STATUS_ERROR);
     }
   }
+
+  private static class RetraceDiagnosticsHandler implements DiagnosticsHandler {
+
+    private final DiagnosticsHandler diagnosticsHandler;
+    private final boolean printInfo;
+
+    public RetraceDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler, boolean printInfo) {
+      this.diagnosticsHandler = diagnosticsHandler;
+      this.printInfo = printInfo;
+      assert diagnosticsHandler != null;
+    }
+
+    @Override
+    public void error(Diagnostic error) {
+      diagnosticsHandler.error(error);
+    }
+
+    @Override
+    public void warning(Diagnostic warning) {
+      diagnosticsHandler.warning(warning);
+    }
+
+    @Override
+    public void info(Diagnostic info) {
+      if (printInfo) {
+        diagnosticsHandler.info(info);
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index a7f194f..93addc4 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.StringDiagnostic;
 import java.io.IOException;
 import java.util.List;
 import java.util.function.Consumer;
@@ -56,7 +57,7 @@
   public static class Builder {
 
     private boolean isVerbose;
-    private DiagnosticsHandler diagnosticsHandler;
+    private final DiagnosticsHandler diagnosticsHandler;
     private ProguardMapProducer proguardMapProducer;
     private String regularExpression;
     private List<String> stackTrace;
@@ -127,6 +128,11 @@
       if (this.retracedStackTraceConsumer == null) {
         throw new RuntimeException("RetracedStackConsumer not specified");
       }
+      if (isVerbose && regularExpression != null) {
+        this.diagnosticsHandler.warning(
+            new StringDiagnostic(
+                "Retrace does not support verbose output when a regular expression is specified"));
+      }
       return new RetraceCommand(
           isVerbose,
           regularExpression,
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 258d869..ebe1bbf 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -4,13 +4,16 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.ToolHelper.LINE_SEPARATOR;
+import static com.android.tools.r8.retrace.RetraceTests.DEFAULT_REGULAR_EXPRESSION;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
+import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTraceWithInfo;
+import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -22,6 +25,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import org.hamcrest.Matcher;
 import org.junit.Rule;
@@ -30,7 +34,7 @@
 
 public class RetraceCommandLineTests {
 
-  private static final boolean testExternal = true;
+  private static final boolean testExternal = false;
 
   @Rule public TemporaryFolder folder = new TemporaryFolder();
 
@@ -56,7 +60,36 @@
 
   @Test
   public void testVerbose() throws IOException {
-    runAbortTest(containsString("Currently no support for --verbose"), "--verbose");
+    FoundMethodVerboseStackTrace stackTrace = new FoundMethodVerboseStackTrace();
+    runTest(
+        stackTrace.mapping(),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + "\n",
+        "--verbose");
+  }
+
+  @Test
+  public void testRegularExpression() throws IOException {
+    ActualRetraceBotStackTrace stackTrace = new ActualRetraceBotStackTrace();
+    runTest(
+        stackTrace.mapping(),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + "\n",
+        "--regex=" + DEFAULT_REGULAR_EXPRESSION);
+  }
+
+  @Test
+  public void testRegularExpressionWithInfo() throws IOException {
+    ActualRetraceBotStackTraceWithInfo stackTrace = new ActualRetraceBotStackTraceWithInfo();
+    runTest(
+        stackTrace.mapping(),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + "\n",
+        "--regex=" + DEFAULT_REGULAR_EXPRESSION,
+        "--info");
   }
 
   @Test
@@ -66,7 +99,7 @@
 
   @Test
   public void testHelp() throws IOException {
-    ProcessResult processResult = runRetraceCommandLine(null, "--help");
+    ProcessResult processResult = runRetraceCommandLine(null, Arrays.asList("--help"));
     assertEquals(0, processResult.exitCode);
     assertEquals(Retrace.USAGE_MESSAGE, processResult.stdout);
   }
@@ -82,34 +115,39 @@
           "    at r8.retrace(App:184)",
           "    ... 7 more");
 
-  private void runTest(String mapping, String stackTrace, boolean stacktraceStdIn, String expected)
+  private void runTest(
+      String mapping, String stackTrace, boolean stacktraceStdIn, String expected, String... args)
       throws IOException {
-    ProcessResult result = runRetrace(mapping, stackTrace, stacktraceStdIn);
+    ProcessResult result = runRetrace(mapping, stackTrace, stacktraceStdIn, args);
     assertEquals(0, result.exitCode);
     assertEquals(expected, result.stdout);
   }
 
   private void runAbortTest(Matcher<String> errorMatch, String... args) throws IOException {
-    ProcessResult result = runRetraceCommandLine(null, args);
+    ProcessResult result = runRetraceCommandLine(null, Arrays.asList(args));
     assertEquals(1, result.exitCode);
     assertThat(result.stderr, errorMatch);
   }
 
-  private ProcessResult runRetrace(String mapping, String stackTrace, boolean stacktraceStdIn)
+  private ProcessResult runRetrace(
+      String mapping, String stackTrace, boolean stacktraceStdIn, String... additionalArgs)
       throws IOException {
     Path mappingFile = folder.newFile("mapping.txt").toPath();
     Files.write(mappingFile, mapping.getBytes());
     File stackTraceFile = folder.newFile("stacktrace.txt");
     Files.write(stackTraceFile.toPath(), stackTrace.getBytes());
-    if (stacktraceStdIn) {
-      return runRetraceCommandLine(stackTraceFile, mappingFile.toString());
-    } else {
-      return runRetraceCommandLine(
-          null, mappingFile.toString(), stackTraceFile.toPath().toString());
+
+    Collection<String> args = new ArrayList<>();
+    args.add(mappingFile.toString());
+    if (!stacktraceStdIn) {
+      args.add(stackTraceFile.toPath().toString());
     }
+    args.addAll(Arrays.asList(additionalArgs));
+    return runRetraceCommandLine(stacktraceStdIn ? stackTraceFile : null, args);
   }
 
-  private ProcessResult runRetraceCommandLine(File stdInput, String... args) throws IOException {
+  private ProcessResult runRetraceCommandLine(File stdInput, Collection<String> args)
+      throws IOException {
     if (testExternal) {
       List<String> command = new ArrayList<>();
       command.add(ToolHelper.getSystemJavaExecutable());
@@ -117,7 +155,7 @@
       command.add("-cp");
       command.add(ToolHelper.R8_JAR.toString());
       command.add("com.android.tools.r8.retrace.Retrace");
-      command.addAll(Arrays.asList(args));
+      command.addAll(args);
       ProcessBuilder builder = new ProcessBuilder(command);
       if (stdInput != null) {
         builder.redirectInput(stdInput);
@@ -136,7 +174,9 @@
       System.setErr(new PrintStream(errorByteStream));
       int exitCode = 0;
       try {
-        Retrace.run(args);
+        String[] strArgs = new String[0];
+        strArgs = args.toArray(strArgs);
+        Retrace.run(strArgs);
       } catch (Throwable t) {
         exitCode = 1;
       }
@@ -149,7 +189,7 @@
           exitCode,
           outputByteStream.toString(),
           errorByteStream.toString(),
-          StringUtils.join(LINE_SEPARATOR, args));
+          StringUtils.joinLines(args));
     }
   }
 }
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 59a67ad..e29c928 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -43,7 +43,7 @@
   // This is a slight modification of the default regular expression shown for proguard retrace
   // that allow for retracing classes in the form <class>: lorem ipsum...
   // Seems like Proguard retrace is expecting the form "Caused by: <class>".
-  private static final String DEFAULT_REGULAR_EXPRESSION =
+  public static final String DEFAULT_REGULAR_EXPRESSION =
       "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)|(?:(?:(?:%c|.*)?[:\"]\\s+)?%c(?::.*)?)";
 
   @Parameters(name = "{0}, use regular expression: {1}")
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
new file mode 100644
index 0000000..53aa7c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2019, 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.retrace.stacktraces;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ActualRetraceBotStackTraceWithInfo extends ActualBotStackTraceBase {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "com.android.tools.r8.CompilationFailedException: Compilation failed to complete",
+        "\tat com.android.tools.r8.BaseCommand$Builder.build(:6)",
+        "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:104)",
+        "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:29)",
+        "\tat com.android.tools.r8.TestCompilerBuilder.compile(TestCompilerBuilder.java:89)",
+        "\tat com.android.tools.r8.TestCompilerBuilder.run(TestCompilerBuilder.java:113)",
+        "\tat com.android.tools.r8.TestBuilder.run(TestBuilder.java:49)",
+        "\tat com.android.tools.r8.ir.optimize.classinliner.ClassInlinerTest.testCodeSample(ClassInlinerTest.java:289)",
+        "",
+        "Caused by:",
+        "com.android.tools.r8.utils.b: Error: offset: 158, line: 2, column: 33, Unexpected"
+            + " attribute at <no file>:2:33",
+        "-keepattributes -keepattributes LineNumberTable",
+        "                                ^",
+        "\tat com.android.tools.r8.utils.t0.a(:21)",
+        "\tat com.android.tools.r8.shaking.ProguardConfigurationParser.parse(:19)",
+        "\tat com.android.tools.r8.R8Command$Builder.i(:16)",
+        "\tat com.android.tools.r8.R8Command$Builder.b(:11)",
+        "\tat com.android.tools.r8.R8Command$Builder.b(:1)",
+        "\tat com.android.tools.r8.BaseCommand$Builder.build(:2)",
+        "\t... 6 more");
+  }
+
+  @Override
+  public String mapping() {
+    return r8MappingFromGitSha("dab96bbe5948133f0ae6e0a88fc133464421cf47");
+  }
+
+  // The Pruning lines is debug info printed by the command line interface if one passes --info and
+  // --regex=<DEFAULT_REGULAR_EXPRESSION> as options.
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Pruning \tat com.android.tools.r8.utils.Reporter.error(Reporter.java:21) from result"
+            + " because method is not defined on line number 21",
+        "Pruning \tat com.android.tools.r8.utils.Reporter.error(Reporter.java:21) from result"
+            + " because method is not defined on line number 21",
+        "Pruning \tat com.android.tools.r8.utils.Reporter.fatalError(Reporter.java:21) from result"
+            + " because method is not defined on line number 21",
+        "Pruning \tat"
+            + " com.android.tools.r8.utils.Reporter.addSuppressedExceptions(Reporter.java:21) from"
+            + " result because method is not defined on line number 21",
+        "Pruning \tat"
+            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
+            + " from result because method is not defined on line number 19",
+        "Pruning \tat"
+            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
+            + " from result because method is not defined on line number 19",
+        "Pruning \tat"
+            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
+            + " from result because method is not in range on line number 19",
+        "Pruning \tat"
+            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
+            + " from result because method is not in range on line number 19",
+        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:11) from"
+            + " result because method is not defined on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.setDisableVerticalClassMerging(R8Command.java:11)"
+            + " from result because method is not defined on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfigurationFiles$4(R8Command.java:11)"
+            + " from result because method is not defined on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:11)"
+            + " from result because method is not defined on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:11)"
+            + " from result because method is not defined on line number 11",
+        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:11) from"
+            + " result because method is not in range on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.setDisableVerticalClassMerging(R8Command.java:1)"
+            + " from result because method is not defined on line number 1",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfigurationFiles$4(R8Command.java:1)"
+            + " from result because method is not defined on line number 1",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:1)"
+            + " from result because method is not defined on line number 1",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:1)"
+            + " from result because method is not defined on line number 1",
+        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:1) from"
+            + " result because method is not defined on line number 1",
+        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:1) from"
+            + " result because method is not defined on line number 1",
+        "com.android.tools.r8.CompilationFailedException: Compilation failed to complete",
+        "\tat com.android.tools.r8.BaseCommand$Builder.build(BaseCommand.java:143)",
+        "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:104)",
+        "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:29)",
+        "\tat com.android.tools.r8.TestCompilerBuilder.compile(TestCompilerBuilder.java:89)",
+        "\tat com.android.tools.r8.TestCompilerBuilder.run(TestCompilerBuilder.java:113)",
+        "\tat com.android.tools.r8.TestBuilder.run(TestBuilder.java:49)",
+        "\tat com.android.tools.r8.ir.optimize.classinliner.ClassInlinerTest.testCodeSample(ClassInlinerTest.java:289)",
+        "",
+        "Caused by:",
+        "com.android.tools.r8.utils.AbortException: Error: offset: 158, line: 2, column: 33,"
+            + " Unexpected attribute at <no file>:2:33",
+        "-keepattributes -keepattributes LineNumberTable",
+        "                                ^",
+        "\tat com.android.tools.r8.utils.Reporter.failIfPendingErrors(Reporter.java:101)",
+        "\tat com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:187)",
+        "\tat com.android.tools.r8.R8Command$Builder.makeR8Command(R8Command.java:432)",
+        "\tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:413)",
+        "\tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:61)",
+        "\tat com.android.tools.r8.BaseCommand$Builder.build(BaseCommand.java:139)",
+        "\t... 6 more");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 1;
+  }
+}