Add test for normalizing proto to virtual method defined in library

Bug: b/258720808
Change-Id: Ib2e17221cb013005e1410deff257cc72df4e1558
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
new file mode 100644
index 0000000..3b7a115
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
@@ -0,0 +1,111 @@
+// 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.optimize.proto;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 ProtoNormalizationDestinationOverrideLibraryTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String[] EXPECTED = new String[] {"LibraryMethod421337"};
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, ProgramClass.class, X.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryClasses(LibraryClass.class)
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/258720808): We should not fail compilation.
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(Main.class, ProgramClass.class, X.class)
+                .addDefaultRuntimeLibrary(parameters)
+                .addLibraryClasses(LibraryClass.class)
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(Main.class)
+                .addDontObfuscate()
+                .enableInliningAnnotations()
+                .enableNoMethodStaticizingAnnotations()
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics.assertErrorMessageThatMatches(
+                          containsString(
+                              "went from not overriding a library method to overriding a library"
+                                  + " method"));
+                    }));
+  }
+
+  public static class LibraryClass {
+
+    public void foo(int i1, int i2, String bar) {
+      System.out.println(bar + i1 + i2);
+    }
+
+    public static void callFoo(LibraryClass clazz) {
+      clazz.foo(42, 1337, "LibraryMethod");
+    }
+  }
+
+  public static class ProgramClass extends LibraryClass {
+    @NeverInline
+    @NoMethodStaticizing
+    public void foo(String bar, int i1, int i2) {
+      System.out.println(i1 + i2 + bar);
+    }
+  }
+
+  // Needs to have a class name that is after lexicographically than ProgramClass.
+  public static class X {
+    @NeverInline
+    public void foo(int i1, String bar, int i2) {
+      System.out.println(i1 + bar + i2);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ProgramClass programClass = new ProgramClass();
+      X otherProgramClass = new X();
+      if (args.length == 1) {
+        programClass.foo("Hello World", 1, 1);
+        otherProgramClass.foo(1, "Hello World", 1);
+      } else if (args.length > 1) {
+        programClass.foo("Goodbye World", 2, 2);
+        otherProgramClass.foo(2, "Goodbye World", 2);
+      }
+      LibraryClass.callFoo(programClass);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
index ba776e4..c179493 100644
--- a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
@@ -33,6 +33,20 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private final String[] EXPECTED =
+      new String[] {"A::foo", "B", "A", "B::foo", "B", "A", "B::foo", "B", "A"};
+
+  private final String[] R8_EXPECTED =
+      new String[] {"A::foo", "B", "A", "A::foo", "B", "A", "B::foo", "B", "A"};
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
@@ -40,20 +54,23 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
-        // TODO(b/173398086): uniqueMethodWithName() does not work with proto changes.
         .addDontObfuscate()
+        .addKeepClassAndMembersRules(B.class)
         .setMinApi(parameters.getApiLevel())
         .compile()
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/258720808): We should not produce incorrect results.
+        .assertSuccessWithOutputLines(R8_EXPECTED)
         .inspect(
             inspector -> {
-              ClassSubject aClassSubject = inspector.clazz(A.class);
-              assertThat(aClassSubject, isPresent());
-
               ClassSubject bClassSubject = inspector.clazz(B.class);
               assertThat(bClassSubject, isPresent());
 
-              TypeSubject aTypeSubject = aClassSubject.asTypeSubject();
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
               TypeSubject bTypeSubject = bClassSubject.asTypeSubject();
+              TypeSubject aTypeSubject = aClassSubject.asTypeSubject();
 
               MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithOriginalName("foo");
               assertThat(fooMethodSubject, isPresent());
@@ -62,12 +79,10 @@
               // TODO(b/173398086): Consider rewriting B.foo(B, A) to B.foo(A, B, C) instead of
               //  B.foo$1(A, B).
               MethodSubject otherFooMethodSubject =
-                  bClassSubject.uniqueMethodWithOriginalName("foo$1");
+                  bClassSubject.uniqueMethodWithOriginalName("foo");
               assertThat(otherFooMethodSubject, isPresent());
               assertThat(otherFooMethodSubject, hasParameters(aTypeSubject, bTypeSubject));
-            })
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("A", "B", "A", "B");
+            });
   }
 
   static class Main {
@@ -75,16 +90,18 @@
     public static void main(String[] args) {
       A a = new A();
       B b = new B();
+      a.foo(b, a);
       a.foo(a, b);
-      b.foo(b, a);
+      b.foo(a, b);
     }
   }
 
   @NoVerticalClassMerging
-  static class A {
+  static class B {
 
     @NeverInline
     public void foo(A a, B b) {
+      System.out.println("B::foo");
       System.out.println(a);
       System.out.println(b);
     }
@@ -95,10 +112,11 @@
     }
   }
 
-  static class B extends A {
+  static class A extends B {
 
     @NeverInline
     public void foo(B b, A a) {
+      System.out.println("A::foo");
       System.out.println(a);
       System.out.println(b);
     }