Add test for using applymapping with devirtualized interfaces

The MethodNameMinifier will reserve names of interfaces that may have
been merged in an optimized library - and not pick names from the
strategy. This can result in method not found exception at runtime but
also compile time errors because of name-clashes.

This CL introduces tests to highlight the problem.

Bug: 126503704
Bug: 123092153
Bug: 121305642
Change-Id: I9ec3faa0dcbe82acd0988adaaf7903f602a546f4
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 2433bbb..511f328 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -82,6 +82,13 @@
     return self();
   }
 
+  public T addKeepClassAndMembersRulesWithAllowObfuscation(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      addKeepRules("-keep,allowobfuscation class " + clazz.getTypeName() + " { *; }");
+    }
+    return self();
+  }
+
   public T addKeepClassAndDefaultConstructor(Class<?>... classes) {
     for (Class<?> clazz : classes) {
       addKeepRules("-keep class " + clazz.getTypeName() + " { <init>(); }");
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
new file mode 100644
index 0000000..7ef141e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
@@ -0,0 +1,162 @@
+// 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.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApplyMappingAfterDevirtualizationTest extends TestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("LibClassA::foo", "LibClassB::foo", "LibClassB::bar");
+
+  public interface LibInterfaceA {
+    void foo();
+  }
+
+  public interface LibInterfaceB {
+    void foo();
+  }
+
+  // LibInterfaceA should be devirtualized into LibClassA
+  public static class LibClassA implements LibInterfaceA {
+
+    @Override
+    public void foo() {
+      System.out.println("LibClassA::foo");
+    }
+  }
+
+  // LibClassB should be devirtualized into LibInterfaceB
+  public static class LibClassB implements LibInterfaceB {
+
+    @Override
+    public void foo() {
+      System.out.println("LibClassB::foo");
+    }
+
+    public void bar() {
+      System.out.println("LibClassB::bar");
+    }
+
+    public static void main(String[] args) {
+      if (args.length > 0) {
+        new LibClassB().foo();
+      } else {
+        new LibClassB().bar();
+      }
+    }
+  }
+
+  public static class ProgramClass {
+
+    public static void main(String[] args) {
+      new LibClassA().foo();
+      LibClassB libClassB = new LibClassB();
+      libClassB.foo();
+      libClassB.bar();
+    }
+  }
+
+  private static final Class<?>[] LIBRARY_CLASSES = {
+    LibInterfaceA.class, LibInterfaceB.class, LibClassA.class, LibClassB.class
+  };
+
+  private static final Class<?>[] PROGRAM_CLASSES = {ProgramClass.class};
+
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public ApplyMappingAfterDevirtualizationTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void runOnJvm() throws Throwable {
+    Assume.assumeTrue(backend == Backend.CF);
+    testForJvm()
+        .addProgramClasses(LIBRARY_CLASSES)
+        .addProgramClasses(PROGRAM_CLASSES)
+        .run(ProgramClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Ignore("b/126503704")
+  @Test
+  public void devirtualizingNoRenamingOfOverridenNotKeptInterfaceMethods() throws Exception {
+    R8TestCompileResult libraryResult =
+        testForR8(backend)
+            .addProgramClasses(LIBRARY_CLASSES)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class)
+            .addKeepMainRule(LibClassB.class)
+            .addOptionsModification(options -> options.enableInlining = false)
+            .compile();
+
+    CodeInspector inspector = libraryResult.inspector();
+    assertThat(inspector.clazz(LibClassA.class), isPresent());
+    assertThat(inspector.clazz(LibClassB.class), isPresent());
+
+    // LibInterfaceX should have been moved into LibClassX.
+    assertThat(inspector.clazz(LibInterfaceA.class), not(isPresent()));
+    assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent()));
+
+    testForR8(backend)
+        .noTreeShaking()
+        .noMinification()
+        .addProgramClasses(PROGRAM_CLASSES)
+        .addApplyMapping(libraryResult.getProguardMap())
+        .addLibraryClasses(LIBRARY_CLASSES)
+        .compile()
+        .addRunClasspathFiles(libraryResult.writeToZip())
+        .run(ProgramClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Ignore("b/126503704")
+  @Test
+  public void devirtualizingNoRenamingOfOverridenKeptInterfaceMethods() throws Exception {
+    R8TestCompileResult libraryResult =
+        testForR8(backend)
+            .addProgramClasses(LIBRARY_CLASSES)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class, LibInterfaceA.class)
+            .addKeepMainRule(LibClassB.class)
+            .addOptionsModification(options -> options.enableInlining = false)
+            .compile();
+
+    CodeInspector inspector = libraryResult.inspector();
+    assertThat(inspector.clazz(LibClassA.class), isPresent());
+    assertThat(inspector.clazz(LibClassB.class), isPresent());
+
+    // LibInterfaceA is now kept.
+    assertThat(inspector.clazz(LibInterfaceA.class), isPresent());
+    assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent()));
+
+    testForR8(backend)
+        .noTreeShaking()
+        .noMinification()
+        .addProgramClasses(PROGRAM_CLASSES)
+        .addApplyMapping(libraryResult.getProguardMap())
+        .addLibraryClasses(LIBRARY_CLASSES)
+        .compile()
+        .addRunClasspathFiles(libraryResult.writeToZip())
+        .run(ProgramClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+}