Test for retracing code with desugared lambdas.

Bug: 172014416
Change-Id: Ief53386edc499de347ca408b21364eacd81898a4
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 1f7101b..34d84c4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.retrace.Retrace;
 import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.utils.StringUtils;
@@ -17,6 +18,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -54,6 +56,10 @@
       return addWithoutFileNameAndLineNumber(clazz.getTypeName(), methodName);
     }
 
+    public Builder addWithoutFileNameAndLineNumber(ClassReference clazz, String methodName) {
+      return addWithoutFileNameAndLineNumber(clazz.getTypeName(), methodName);
+    }
+
     public Builder addWithoutFileNameAndLineNumber(String className, String methodName) {
       stackTraceLines.add(
           StackTraceLine.builder().setClassName(className).setMethodName(methodName).build());
@@ -65,6 +71,13 @@
       return this;
     }
 
+    public Builder applyIf(boolean condition, Consumer<Builder> fn) {
+      if (condition) {
+        fn.accept(this);
+      }
+      return this;
+    }
+
     public StackTrace build() {
       return new StackTrace(
           stackTraceLines,
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
new file mode 100644
index 0000000..aa543c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -0,0 +1,161 @@
+// 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.
+
+package com.android.tools.r8.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceLambdaTest extends TestBase {
+
+  private static final String JAVAC_LAMBDA_METHOD = "lambda$main$0";
+
+  // TODO(b/172014416): These should not be needed once fixed.
+  private static final String LAMBDA_BRIDGE_METHOD = "$r8$lambda$dX5OYTAgq4ijGUv_zaGoVsFINMs";
+  private static final String INTERNAL_LAMBDA_CLASS =
+      Main.class.getTypeName()
+          + "$$InternalSyntheticLambda$0$11a5d582ed94e937718cf3ed497d4d164b60dfa85d606466457007fade57dce8$0";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private final TestParameters parameters;
+
+  public RetraceLambdaTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .inspectStackTrace(
+            stackTrace -> {
+              assertThat(
+                  stackTrace,
+                  isSameExceptForFileNameAndLineNumber(
+                      StackTrace.builder()
+                          .addWithoutFileNameAndLineNumber(Main.class, JAVAC_LAMBDA_METHOD)
+                          // TODO(b/172014416): Support a D8 mapping and prune the synthetic.
+                          .applyIf(
+                              parameters.isDexRuntime(),
+                              b ->
+                                  b.addWithoutFileNameAndLineNumber(
+                                      SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0),
+                                      "run"))
+                          .addWithoutFileNameAndLineNumber(Main.class, "runIt")
+                          .addWithoutFileNameAndLineNumber(Main.class, "main")
+                          .build()));
+            });
+  }
+
+  @Test
+  public void testEverythingInlined() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .inspectStackTrace(
+            stackTrace -> {
+              int frames = parameters.isCfRuntime() ? 2 : 1;
+              checkRawStackTraceFrameCount(stackTrace, frames, "Expected everything to be inlined");
+              checkCurrentlyIncorrectStackTrace(stackTrace, JAVAC_LAMBDA_METHOD);
+            });
+  }
+
+  @Test
+  public void testNothingInlined() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepPackageNamesRule(getClass().getPackage())
+        .noTreeShaking()
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .inspectStackTrace(
+            stackTrace -> {
+              int frames = parameters.isCfRuntime() ? 3 : 5;
+              checkRawStackTraceFrameCount(stackTrace, frames, "Expected nothing to be inlined");
+              checkCurrentlyIncorrectStackTrace(stackTrace, "lambda$main$0");
+            });
+  }
+
+  private void checkRawStackTraceFrameCount(
+      StackTrace stackTrace, int expectedFrames, String message) {
+    int linesFromTest = 0;
+    for (String line : stackTrace.getOriginalStderr().split("\n")) {
+      if (line.trim().startsWith("at " + getClass().getPackage().getName())) {
+        linesFromTest++;
+      }
+    }
+    assertEquals(message + stackTrace.getOriginalStderr(), expectedFrames, linesFromTest);
+  }
+
+  private void checkCurrentlyIncorrectStackTrace(StackTrace stackTrace, String javacLambdaMethod) {
+    assertThat(
+        stackTrace,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .addWithoutFileNameAndLineNumber(Main.class, javacLambdaMethod)
+                .applyIf(
+                    parameters.isDexRuntime(),
+                    b ->
+                        b
+                            // TODO(b/172014416): Lambda bridges should be marked synthetic
+                            //  and removed.
+                            .addWithoutFileNameAndLineNumber(Main.class, LAMBDA_BRIDGE_METHOD)
+                            // TODO(b/172014416): The frame mapping should have removed this
+                            //  entry.
+                            // TODO(b/172014416): Synthetics should not map back to internal
+                            //  names.
+                            .addWithoutFileNameAndLineNumber(INTERNAL_LAMBDA_CLASS, "run"))
+                .addWithoutFileNameAndLineNumber(Main.class, "runIt")
+                .addWithoutFileNameAndLineNumber(Main.class, "main")
+                .build()));
+  }
+
+  public interface MyRunner {
+    void run();
+  }
+
+  public static class Main {
+
+    public static void runIt(MyRunner runner) {
+      runner.run();
+    }
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        runIt(
+            () -> {
+              throw new RuntimeException("Hello World!");
+            });
+      }
+    }
+  }
+}