Add matchers for asserting inline information

Before we would only match on the inline information provided by a
stack trace - or check if a method no longer existed in the output.

This CL add matchers around RetraceMethodResult, allowing one to
specify exactly the inline stack for an instruction.

Bug: 145903423
Change-Id: I022ebe32b1e0a186913d7369caf8c72ea21ac9ab
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 1f95782..d549e28 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -179,6 +179,10 @@
     return false;
   }
 
+  public boolean isThrow() {
+    return false;
+  }
+
   public int getPayloadOffset() {
     return 0;
   }
diff --git a/src/main/java/com/android/tools/r8/code/Throw.java b/src/main/java/com/android/tools/r8/code/Throw.java
index 247b475..40d69f1 100644
--- a/src/main/java/com/android/tools/r8/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/code/Throw.java
@@ -43,4 +43,9 @@
   public boolean canThrow() {
     return true;
   }
+
+  @Override
+  public boolean isThrow() {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 1a1d4ef..147c251 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -419,6 +419,16 @@
       }
     }
 
+    public int getFirstLineNumberOfOriginalRange() {
+      if (originalRange == null) {
+        return 0;
+      } else if (originalRange instanceof Integer) {
+        return (int) originalRange;
+      } else {
+        return ((Range) originalRange).from;
+      }
+    }
+
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/retrace/Result.java b/src/main/java/com/android/tools/r8/retrace/Result.java
index 2940b12..12e92d3 100644
--- a/src/main/java/com/android/tools/r8/retrace/Result.java
+++ b/src/main/java/com/android/tools/r8/retrace/Result.java
@@ -6,9 +6,12 @@
 
 import com.android.tools.r8.Keep;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 @Keep
 public abstract class Result<R, RR extends Result<R, RR>> {
 
+  public abstract Stream<R> stream();
+
   public abstract RR forEach(Consumer<R> resultConsumer);
 }
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 6a4c2e3..79d9721 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -116,7 +116,7 @@
     try {
       ClassNameMapper classNameMapper =
           ClassNameMapper.mapperFromString(command.proguardMapProducer.get());
-      RetraceBase retraceBase = new RetraceBaseImpl(classNameMapper);
+      RetraceBase retraceBase = RetraceBaseImpl.create(classNameMapper);
       RetraceCommandLineResult result;
       if (command.regularExpression != null) {
         result =
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
index c60cd91..9a68706 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
@@ -21,10 +21,14 @@
 
   private final ClassNameMapper classNameMapper;
 
-  RetraceBaseImpl(ClassNameMapper classNameMapper) {
+  private RetraceBaseImpl(ClassNameMapper classNameMapper) {
     this.classNameMapper = classNameMapper;
   }
 
+  public static RetraceBase create(ClassNameMapper classNameMapper) {
+    return new RetraceBaseImpl(classNameMapper);
+  }
+
   @Override
   public RetraceMethodResult retrace(MethodReference methodReference) {
     return retrace(methodReference.getHolderClass()).lookupMethod(methodReference.getMethodName());
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index ee21fd2..32e533d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -80,7 +80,7 @@
     return mapper != null;
   }
 
-  Stream<Element> stream() {
+  public Stream<Element> stream() {
     return Stream.of(
         new Element(
             this,
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index 733eec7..38eb7c9 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -48,7 +48,7 @@
     return memberNamings.size() > 1;
   }
 
-  Stream<Element> stream() {
+  public Stream<Element> stream() {
     if (!hasRetraceResult()) {
       return Stream.of(
           new Element(
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index 347c646..cb47139 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -90,7 +90,7 @@
         classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName);
   }
 
-  Stream<Element> stream() {
+  public Stream<Element> stream() {
     if (!hasRetraceResult()) {
       return Stream.of(new Element(this, classElement, getUnknownReference(), null));
     }
@@ -160,5 +160,12 @@
     public int getOriginalLineNumber(int linePosition) {
       return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
     }
+
+    public int getFirstLineNumberOfOriginalRange() {
+      if (mappedRange == null) {
+        return 0;
+      }
+      return mappedRange.getFirstLineNumberOfOriginalRange();
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/Collectors.java b/src/test/java/com/android/tools/r8/Collectors.java
new file mode 100644
index 0000000..0e05ce0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/Collectors.java
@@ -0,0 +1,19 @@
+// 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;
+
+import java.util.stream.Collector;
+
+public abstract class Collectors {
+
+  public static <T> Collector<T, ?, T> toSingle() {
+    return java.util.stream.Collectors.collectingAndThen(
+        java.util.stream.Collectors.toList(),
+        items -> {
+          assert items.size() == 1;
+          return items.get(0);
+        });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 88a75bd..50eb3be 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
@@ -69,12 +70,19 @@
     return graphInspector.get();
   }
 
-  public R8TestRunResult  inspectGraph(Consumer<GraphInspector> consumer)
+  public R8TestRunResult inspectGraph(Consumer<GraphInspector> consumer)
       throws IOException, ExecutionException {
     consumer.accept(graphInspector());
     return self();
   }
 
+  public <E extends Throwable> R8TestRunResult inspectStackTrace(
+      ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer)
+      throws E, IOException, ExecutionException {
+    consumer.accept(getStackTrace(), new CodeInspector(app, proguardMap));
+    return self();
+  }
+
   public String proguardMap() {
     return proguardMap;
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index b469732..986bd3b 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -4,20 +4,29 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
@@ -64,156 +73,155 @@
   public void testRetraceKotlinInlineStaticFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainKt";
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(
-                kotlinc(
-                        parameters.isCfRuntime()
-                            ? parameters.getRuntime().asCf()
-                            : TestRuntime.getCheckedInJdk9(),
-                        KOTLINC,
-                        KotlinTargetVersion.JAVA_8)
-                    .addSourceFiles(
-                        getFilesInTestFolderRelativeToClass(
-                            KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
-                    .compile())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-            .addKeepAttributes("SourceFile", "LineNumberTable")
-            .setMode(CompilationMode.RELEASE)
-            .addKeepMainRule(main)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), main)
-            .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
-            .inspectStackTrace(
-                stackTrace -> {
-                  // TODO(b/b/145903423): Strengthen assertions by using retrace api directly.
-                  assertThat(
-                      stackTrace.get(0).originalLine,
-                      containsString(
-                          "at retrace.InlineFunctionKt.inlineExceptionStatic"
-                              + "(InlineFunctionKt.kt:8)"));
-                  assertThat(
-                      stackTrace.get(1).originalLine,
-                      containsString("at " + main + ".main(Main.kt:15)"));
-                });
+    testForR8(parameters.getBackend())
+        .addProgramFiles(
+            kotlinc(
+                    parameters.isCfRuntime()
+                        ? parameters.getRuntime().asCf()
+                        : TestRuntime.getCheckedInJdk9(),
+                    KOTLINC,
+                    KotlinTargetVersion.JAVA_8)
+                .addSourceFiles(
+                    getFilesInTestFolderRelativeToClass(
+                        KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+                .compile())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(main)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
   }
 
   @Test
   public void testRetraceKotlinInlineInstanceFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainInstanceKt";
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(
-                kotlinc(
-                        parameters.isCfRuntime()
-                            ? parameters.getRuntime().asCf()
-                            : TestRuntime.getCheckedInJdk9(),
-                        KOTLINC,
-                        KotlinTargetVersion.JAVA_8)
-                    .addSourceFiles(
-                        getFilesInTestFolderRelativeToClass(
-                            KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
-                    .compile())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-            .addKeepAttributes("SourceFile", "LineNumberTable")
-            .setMode(CompilationMode.RELEASE)
-            .addKeepMainRule(main)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), main)
-            .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
-            .inspectStackTrace(
-                // TODO(b/b/145903423): Strengthen assertions by using retrace api directly.
-                stackTrace -> {
-                  assertThat(
-                      stackTrace.get(0).originalLine,
-                      containsString(
-                          "at retrace.InlineFunction.inlineExceptionInstance"
-                              + "(InlineFunction.kt:15)"));
-                  assertThat(
-                      stackTrace.get(1).originalLine,
-                      containsString("at " + main + ".main(MainInstance.kt:13)"));
-                });
+    testForR8(parameters.getBackend())
+        .addProgramFiles(
+            kotlinc(
+                    parameters.isCfRuntime()
+                        ? parameters.getRuntime().asCf()
+                        : TestRuntime.getCheckedInJdk9(),
+                    KOTLINC,
+                    KotlinTargetVersion.JAVA_8)
+                .addSourceFiles(
+                    getFilesInTestFolderRelativeToClass(
+                        KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+                .compile())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(main)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          "retrace.InlineFunction", "inlineExceptionInstance", 2, 15),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
   }
 
   @Test
   public void testRetraceKotlinNestedInlineFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainNestedKt";
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(
-                kotlinc(
-                        parameters.isCfRuntime()
-                            ? parameters.getRuntime().asCf()
-                            : TestRuntime.getCheckedInJdk9(),
-                        KOTLINC,
-                        KotlinTargetVersion.JAVA_8)
-                    .addSourceFiles(
-                        getFilesInTestFolderRelativeToClass(
-                            KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
-                    .compile())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-            .addKeepAttributes("SourceFile", "LineNumberTable")
-            .setMode(CompilationMode.RELEASE)
-            .addKeepMainRule(main)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), main)
-            .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
-            .inspectStackTrace(
-                stackTrace -> {
-                  // TODO(b/b/145903423): Strengthen assertions by using retrace api directly.
-                  assertThat(
-                      stackTrace.get(0).originalLine,
-                      containsString(
-                          "at retrace.InlineFunctionKt.inlineExceptionStatic"
-                              + "(InlineFunctionKt.kt:8)"));
-                  assertThat(
-                      stackTrace.get(1).originalLine,
-                      containsString(
-                          "at retrace.NestedInlineFunctionKt.nestedInline"
-                              + "(NestedInlineFunctionKt.kt:10)"));
-                  assertThat(
-                      stackTrace.get(2).originalLine,
-                      containsString("at " + main + ".main(MainNested.kt:19)"));
-                });
+    testForR8(parameters.getBackend())
+        .addProgramFiles(
+            kotlinc(
+                    parameters.isCfRuntime()
+                        ? parameters.getRuntime().asCf()
+                        : TestRuntime.getCheckedInJdk9(),
+                    KOTLINC,
+                    KotlinTargetVersion.JAVA_8)
+                .addSourceFiles(
+                    getFilesInTestFolderRelativeToClass(
+                        KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+                .compile())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(main)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 3, 8),
+                      InlinePosition.create(
+                          "retrace.NestedInlineFunctionKt", "nestedInline", 3, 10),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
   }
 
   @Test
   public void testRetraceKotlinNestedInlineFunctionOnFirstLine()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainNestedFirstLineKt";
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(
-                kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
-                    .addSourceFiles(
-                        getFilesInTestFolderRelativeToClass(
-                            KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
-                    .compile())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-            .addKeepAttributes("SourceFile", "LineNumberTable")
-            .setMode(CompilationMode.RELEASE)
-            .addKeepMainRule(main)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), main)
-            .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
-            .inspectStackTrace(
-                stackTrace -> {
-                  // TODO(b/b/145903423): Strengthen assertions by using retrace api directly.
-                  assertThat(
-                      stackTrace.get(0).originalLine,
-                      containsString(
-                          "at retrace.InlineFunctionKt.inlineExceptionStatic"
-                              + "(InlineFunctionKt.kt:8)"));
-                  assertThat(
-                      stackTrace.get(1).originalLine,
-                      containsString(
-                          "at retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine"
-                              + "(NestedInlineFunctionKt.kt:15)"));
-                  assertThat(
-                      stackTrace.get(2).originalLine,
-                      containsString("at " + main + ".main(MainNestedFirstLine.kt:20)"));
-                });
+    testForR8(parameters.getBackend())
+        .addProgramFiles(
+            kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
+                .addSourceFiles(
+                    getFilesInTestFolderRelativeToClass(
+                        KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+                .compile())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(main)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+                      InlinePosition.create(
+                          "retrace.NestedInlineFunctionKt", "nestedInlineOnFirstLine", 2, 15),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
+  }
+
+  private void checkInlineInformation(
+      StackTrace stackTrace,
+      CodeInspector codeInspector,
+      MethodSubject mainSubject,
+      InlinePosition inlineStack) {
+    assertThat(mainSubject, isPresent());
+    RetraceMethodResult retraceResult =
+        mainSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isThrow)
+            .collect(toSingle())
+            .retracePosition(codeInspector.retrace());
+    assertThat(retraceResult, isInlineFrame());
+    assertThat(retraceResult, isInlineStack(inlineStack));
+    assertThat(stackTrace, containsInlinePosition(inlineStack));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 417e40e..1fd4abe 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -6,7 +6,16 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
+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.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceMethodResult.Element;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -339,4 +348,189 @@
       }
     };
   }
+
+  public static Matcher<RetraceMethodResult> isInlineFrame() {
+    return new TypeSafeMatcher<RetraceMethodResult>() {
+      @Override
+      protected boolean matchesSafely(RetraceMethodResult item) {
+        return !item.isAmbiguous() && item.stream().count() > 1;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is not an inline frame");
+      }
+    };
+  }
+
+  public static Matcher<RetraceMethodResult> isInlineStack(InlinePosition startPosition) {
+    return new TypeSafeMatcher<RetraceMethodResult>() {
+      @Override
+      protected boolean matchesSafely(RetraceMethodResult item) {
+        Box<InlinePosition> currentPosition = new Box<>(startPosition);
+        Box<Boolean> returnValue = new Box<>();
+        item.forEach(
+            element -> {
+              boolean sameMethod;
+              InlinePosition currentInline = currentPosition.get();
+              if (currentInline == null) {
+                returnValue.set(false);
+                return;
+              }
+              if (currentInline.hasMethodSubject()) {
+                sameMethod =
+                    element
+                        .getMethodReference()
+                        .equals(
+                            currentInline.methodSubject.asFoundMethodSubject().asMethodReference());
+              } else {
+                MethodReference methodReference = element.getMethodReference();
+                sameMethod =
+                    methodReference.getMethodName().equals(currentInline.methodName)
+                        || methodReference
+                            .getHolderClass()
+                            .getTypeName()
+                            .equals(currentInline.holder);
+              }
+              boolean samePosition =
+                  element.getOriginalLineNumber(currentInline.minifiedPosition)
+                      == currentInline.originalPosition;
+              if (!returnValue.isSet() || returnValue.get()) {
+                returnValue.set(sameMethod & samePosition);
+              }
+              currentPosition.set(currentInline.caller);
+            });
+        return returnValue.isSet() && returnValue.get();
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is not matching the inlining stack");
+      }
+    };
+  }
+
+  public static Matcher<RetraceMethodResult> isInlinedInto(InlinePosition inlinePosition) {
+    return new TypeSafeMatcher<RetraceMethodResult>() {
+      @Override
+      protected boolean matchesSafely(RetraceMethodResult item) {
+        if (item.isAmbiguous() || !inlinePosition.methodSubject.isPresent()) {
+          return false;
+        }
+        List<Element> references = item.stream().collect(Collectors.toList());
+        if (references.size() < 2) {
+          return false;
+        }
+        Element lastElement = ListUtils.last(references);
+        if (!lastElement
+            .getMethodReference()
+            .equals(inlinePosition.methodSubject.asFoundMethodSubject().asMethodReference())) {
+          return false;
+        }
+        return lastElement.getFirstLineNumberOfOriginalRange() == inlinePosition.originalPosition;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is not inlined into " + inlinePosition.getMethodName());
+      }
+    };
+  }
+
+  public static Matcher<StackTrace> containsInlinePosition(InlinePosition inlinePosition) {
+    return new TypeSafeMatcher<StackTrace>() {
+      @Override
+      protected boolean matchesSafely(StackTrace item) {
+        return containsInlineStack(item, 0, inlinePosition);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("cannot be found in stack trace");
+      }
+
+      private boolean containsInlineStack(
+          StackTrace stackTrace, int index, InlinePosition currentPosition) {
+        if (currentPosition == null) {
+          return true;
+        }
+        if (index >= stackTrace.size()) {
+          return false;
+        }
+        StackTraceLine stackTraceLine = stackTrace.get(index);
+        boolean resultHere =
+            stackTraceLine.className.equals(currentPosition.getClassName())
+                && stackTraceLine.methodName.equals(currentPosition.getMethodName())
+                && stackTraceLine.lineNumber == currentPosition.originalPosition;
+        if (resultHere && containsInlineStack(stackTrace, index + 1, currentPosition.caller)) {
+          return true;
+        }
+        // Maybe the inline position starts from the top on the next position.
+        return containsInlineStack(stackTrace, index + 1, inlinePosition);
+      }
+    };
+  }
+
+  public static class InlinePosition {
+    private final FoundMethodSubject methodSubject;
+    private final String holder;
+    private final String methodName;
+    private final int minifiedPosition;
+    private final int originalPosition;
+
+    private InlinePosition caller;
+
+    private InlinePosition(
+        FoundMethodSubject methodSubject,
+        String holder,
+        String methodName,
+        int minifiedPosition,
+        int originalPosition) {
+      this.methodSubject = methodSubject;
+      this.holder = holder;
+      this.methodName = methodName;
+      this.minifiedPosition = minifiedPosition;
+      this.originalPosition = originalPosition;
+      assert methodSubject != null || holder != null;
+      assert methodSubject != null || methodName != null;
+    }
+
+    public static InlinePosition create(
+        FoundMethodSubject methodSubject, int minifiedPosition, int originalPosition) {
+      return new InlinePosition(methodSubject, null, null, minifiedPosition, originalPosition);
+    }
+
+    public static InlinePosition create(
+        String holder, String methodName, int minifiedPosition, int originalPosition) {
+      return new InlinePosition(null, holder, methodName, minifiedPosition, originalPosition);
+    }
+
+    public static InlinePosition stack(InlinePosition... stack) {
+      setCaller(1, stack);
+      return stack[0];
+    }
+
+    private static void setCaller(int index, InlinePosition... stack) {
+      assert index > 0;
+      if (index >= stack.length) {
+        return;
+      }
+      stack[index - 1].caller = stack[index];
+      setCaller(index + 1, stack);
+    }
+
+    boolean hasMethodSubject() {
+      return methodSubject != null;
+    }
+
+    String getMethodName() {
+      return hasMethodSubject() ? methodSubject.getOriginalName(false) : methodName;
+    }
+
+    String getClassName() {
+      return hasMethodSubject()
+          ? methodSubject.asMethodReference().getHolderClass().getTypeName()
+          : holder;
+    }
+  }
 }