Add InterfaceRenamingTest

Bug: 76461545
Change-Id: Ic0ee79a586b1aa3f2697b612a6324d64b2f12ed9
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java
new file mode 100644
index 0000000..d42800a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, 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;
+
+public class InterfaceRenamingTest {
+
+  public static final Class[] CLASSES = {
+    InterfaceRenamingTest.class,
+    InterfaceA.class,
+    InterfaceB.class,
+    ImplementationA1.class,
+    ImplementationB1.class,
+    ImplementationA2.class,
+    ImplementationB2.class,
+  };
+
+  // Since the names and parameter lists of the two methods in the two interfaces are equal,
+  // non-aggressive minification gives the methods the same minified name.
+  // However, the return types are different, so looking up the renamed name by prototype
+  // only gives a result for one of the two interfaces.
+  interface InterfaceA {
+    Boolean interfaceMethod();
+  }
+
+  interface InterfaceB {
+    Integer interfaceMethod();
+  }
+
+  // Two implementations of each interface are required
+  // to avoid the Devirtualizer hiding the buggy behavior.
+  static class ImplementationA1 implements InterfaceA {
+    @Override
+    public Boolean interfaceMethod() {
+      System.out.println("interfaceMethod1");
+      return true;
+    }
+  }
+
+  static class ImplementationA2 implements InterfaceA {
+    @Override
+    public Boolean interfaceMethod() {
+      System.out.println("interfaceMethod1 dummy");
+      return false;
+    }
+  }
+
+  static class ImplementationB1 implements InterfaceB {
+    @Override
+    public Integer interfaceMethod() {
+      System.out.println("interfaceMethod2");
+      return 10;
+    }
+  }
+
+  static class ImplementationB2 implements InterfaceB {
+    @Override
+    public Integer interfaceMethod() {
+      System.out.println("interfaceMethod2 dummy");
+      return 20;
+    }
+  }
+
+  public static void main(String[] args) {
+    invokeA(new ImplementationA1());
+    invokeB(new ImplementationB1());
+    invokeA(new ImplementationA2());
+    invokeB(new ImplementationB2());
+  }
+
+  private static void invokeA(InterfaceA instance) {
+    System.out.println("invokeA: " + instance.interfaceMethod());
+  }
+
+  private static void invokeB(InterfaceB instance) {
+    System.out.println("invokeB: " + instance.interfaceMethod());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
new file mode 100644
index 0000000..d25c061
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2018, 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class InterfaceRenamingTestRunner extends TestBase {
+  static final Class CLASS = InterfaceRenamingTest.class;
+  static final Class[] CLASSES = InterfaceRenamingTest.CLASSES;
+  private boolean bug = false;
+
+  @Test
+  public void testCfNoMinify() throws Exception {
+    testCf(MinifyMode.NONE);
+  }
+
+  @Test
+  public void testCfMinify() throws Exception {
+    bug = true;
+    testCf(MinifyMode.JAVA);
+  }
+
+  @Test
+  public void testCfMinifyAggressive() throws Exception {
+    testCf(MinifyMode.AGGRESSIVE);
+  }
+
+  @Test
+  public void testDexNoMinify() throws Exception {
+    testDex(MinifyMode.NONE);
+  }
+
+  @Test
+  public void testDexMinify() throws Exception {
+    bug = true;
+    testDex(MinifyMode.JAVA);
+  }
+
+  @Test
+  public void testDexMinifyAggressive() throws Exception {
+    testDex(MinifyMode.AGGRESSIVE);
+  }
+
+  private void testCf(MinifyMode minify) throws Exception {
+    ProcessResult runInput =
+        ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+    assertEquals(0, runInput.exitCode);
+    Path outCf = temp.getRoot().toPath().resolve("cf.zip");
+    build(new ClassFileConsumer.ArchiveConsumer(outCf), minify);
+    ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+    if (bug) {
+      assertNotEquals(-1, runCf.stderr.indexOf("java.lang.AbstractMethodError"));
+      assertNotEquals(0, runCf.exitCode);
+      return;
+    }
+    assertEquals(runInput.toString(), runCf.toString());
+  }
+
+  private void testDex(MinifyMode minify) throws Exception {
+    ProcessResult runInput =
+        ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+    assertEquals(0, runInput.exitCode);
+    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+    build(new DexIndexedConsumer.ArchiveConsumer(outDex), minify);
+    if (bug) {
+      ProcessResult runDex = ToolHelper.runArtRaw(outDex.toString(), CLASS.getCanonicalName());
+      assertNotEquals(-1, runDex.stderr.indexOf("java.lang.AbstractMethodError"));
+      assertNotEquals(0, runDex.exitCode);
+      return;
+    }
+    ProcessResult runDex =
+        ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), CLASS.getCanonicalName());
+    assertEquals(runInput.stdout, runDex.stdout);
+    assertEquals(runInput.exitCode, runDex.exitCode);
+  }
+
+  private void build(ProgramConsumer consumer, MinifyMode minify) throws Exception {
+    List<String> config =
+        Arrays.asList(
+            "-keep public class " + CLASS.getCanonicalName() + " {",
+            "  public static void main(...);",
+            "}");
+
+    Builder builder =
+        ToolHelper.addProguardConfigurationConsumer(
+                R8Command.builder(),
+                pgConfig -> {
+                  pgConfig.setPrintMapping(true);
+                  pgConfig.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
+                  if (!minify.isMinify()) {
+                    pgConfig.disableObfuscation();
+                  }
+                })
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(consumer)
+            .addProguardConfiguration(config, Origin.unknown());
+    for (Class<?> c : CLASSES) {
+      builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
+    }
+    if (consumer instanceof ClassFileConsumer) {
+      // TODO(b/75997473): Enable inlining when supported by CF backend
+      ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+    } else {
+      ToolHelper.runR8(builder.build());
+    }
+  }
+}