[Retrace] Add test for retracing inlined pruned method

Bug: b/226885646
Change-Id: I8071d70e8ab4faaa5faa2ea5c4f15114a2af310b
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
index 26c33f4..6891000 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
@@ -10,17 +10,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.transformers.MethodTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.Label;
 
 @RunWith(Parameterized.class)
 public class NoLineInfoTest extends TestBase {
@@ -47,21 +42,7 @@
   private byte[] getTestClassTransformed() throws IOException {
     return transformer(TestClass.class)
         .setSourceFile(INPUT_SOURCE_FILE)
-        .addMethodTransformer(
-            new MethodTransformer() {
-              private final Map<MethodReference, Integer> lines = new HashMap<>();
-
-              @Override
-              public void visitLineNumber(int line, Label start) {
-                Integer nextLine = lines.getOrDefault(getContext().getReference(), 0);
-                if (nextLine > 0) {
-                  super.visitLineNumber(nextLine, start);
-                }
-                // Increment the actual line content by 100 so that each one is clearly distinct
-                // from a PC value for any of the methods.
-                lines.put(getContext().getReference(), nextLine + 100);
-              }
-            })
+        .setPredictiveLineNumbering()
         .transform();
   }
 
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 f4158a1..4fdc4b7 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
@@ -71,6 +71,24 @@
       return this;
     }
 
+    public Builder addWithoutLineNumber(Class<?> clazz, String methodName, String fileName) {
+      return addWithoutLineNumber(clazz.getTypeName(), methodName, fileName);
+    }
+
+    public Builder addWithoutLineNumber(ClassReference clazz, String methodName, String fileName) {
+      return addWithoutLineNumber(clazz.getTypeName(), methodName, fileName);
+    }
+
+    public Builder addWithoutLineNumber(String className, String methodName, String fileName) {
+      stackTraceLines.add(
+          StackTraceLine.builder()
+              .setClassName(className)
+              .setMethodName(methodName)
+              .setFileName(fileName)
+              .build());
+      return this;
+    }
+
     public Builder map(int i, Function<StackTraceLine, StackTraceLine> map) {
       stackTraceLines.set(i, map.apply(stackTraceLines.get(i)));
       return this;
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java b/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java
new file mode 100644
index 0000000..d8e9673
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2022, 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.isSame;
+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.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlineFunctionInPrunedClassTest extends TestBase {
+
+  private static final String NEW_SOURCE_FILE = "SourceFileA.java";
+  private static final String ORIGINAL_SOURCE_FILE = "InlineFunctionInPrunedClassTest.java";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getAWithCustomSourceFile(), getMainWithStaticPosition())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(stackTrace -> checkExpectedStackTrace(stackTrace, false));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getAWithCustomSourceFile(), getMainWithStaticPosition())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              // Ensure we have inlined the A class and it is not in the output.
+              assertEquals(
+                  Sets.newHashSet(typeName(Main.class)),
+                  inspector.allClasses().stream()
+                      .map(FoundClassSubject::getFinalName)
+                      .collect(Collectors.toSet()));
+              checkExpectedStackTrace(stackTrace, true);
+            });
+  }
+
+  private void checkExpectedStackTrace(StackTrace stackTrace, boolean errorInASourceFile) {
+    assertThat(
+        stackTrace,
+        isSame(
+            StackTrace.builder()
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName(typeName(A.class))
+                        .setMethodName("foo")
+                        // TODO(b/226885646): We should keep the original source file.
+                        .setFileName(errorInASourceFile ? ORIGINAL_SOURCE_FILE : NEW_SOURCE_FILE)
+                        .setLineNumber(1)
+                        .build())
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName(typeName(Main.class))
+                        .setMethodName("main")
+                        .setFileName(ORIGINAL_SOURCE_FILE)
+                        .setLineNumber(1)
+                        .build())
+                .build()));
+  }
+
+  private static byte[] getAWithCustomSourceFile() throws Exception {
+    return transformer(A.class)
+        .setSourceFile(NEW_SOURCE_FILE)
+        .setPredictiveLineNumbering(MethodPredicate.all(), 1)
+        .transform();
+  }
+
+  private static byte[] getMainWithStaticPosition() throws Exception {
+    return transformer(Main.class).setPredictiveLineNumbering(MethodPredicate.all(), 1).transform();
+  }
+
+  public static class A {
+
+    public static void foo() {
+      throw new NullPointerException();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 52485b2..cbdbde0 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -32,7 +32,9 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -1064,6 +1066,38 @@
         });
   }
 
+  public ClassFileTransformer setPredictiveLineNumbering() {
+    return setPredictiveLineNumbering(MethodPredicate.all());
+  }
+
+  public ClassFileTransformer setPredictiveLineNumbering(MethodPredicate predicate) {
+    return setPredictiveLineNumbering(predicate, 0);
+  }
+
+  public ClassFileTransformer setPredictiveLineNumbering(
+      MethodPredicate predicate, int startingLineNumber) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          private final Map<MethodReference, Integer> lines = new HashMap<>();
+
+          @Override
+          public void visitLineNumber(int line, Label start) {
+            if (MethodPredicate.testContext(predicate, getContext())) {
+              Integer nextLine =
+                  lines.getOrDefault(getContext().getReference(), startingLineNumber);
+              if (nextLine > 0) {
+                super.visitLineNumber(nextLine, start);
+              }
+              // Increment the actual line content by 100 so that each one is clearly distinct
+              // from a PC value for any of the methods.
+              lines.put(getContext().getReference(), nextLine + 100);
+            } else {
+              super.visitLineNumber(line, start);
+            }
+          }
+        });
+  }
+
   @FunctionalInterface
   private interface VisitMethodInsnCallback {
     void visitMethodInsn(