Add tests for vertical class merging

Change-Id: I8d159cd6a3f0017bad4661f5a472cf88b33df2c6
diff --git a/src/test/examples/classmerging/ConflictingInterfaceSignaturesTest.java b/src/test/examples/classmerging/ConflictingInterfaceSignaturesTest.java
new file mode 100644
index 0000000..223ff37
--- /dev/null
+++ b/src/test/examples/classmerging/ConflictingInterfaceSignaturesTest.java
@@ -0,0 +1,32 @@
+// 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 classmerging;
+
+public class ConflictingInterfaceSignaturesTest {
+
+  public static void main(String[] args) {
+    A a = new InterfaceImpl();
+    a.foo();
+
+    B b = new InterfaceImpl();
+    b.foo();
+  }
+
+  public interface A {
+    void foo();
+  }
+
+  public interface B {
+    void foo();
+  }
+
+  public static final class InterfaceImpl implements A, B {
+
+    @Override
+    public void foo() {
+      System.out.println("In foo on InterfaceImpl");
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/SimpleInterface.java b/src/test/examples/classmerging/SimpleInterface.java
new file mode 100644
index 0000000..e8ebcab
--- /dev/null
+++ b/src/test/examples/classmerging/SimpleInterface.java
@@ -0,0 +1,9 @@
+// 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 classmerging;
+
+public interface SimpleInterface {
+  void foo();
+}
diff --git a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
new file mode 100644
index 0000000..04b5386
--- /dev/null
+++ b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
@@ -0,0 +1,16 @@
+// 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 classmerging;
+
+import classmerging.pkg.SimpleInterfaceImplRetriever;
+
+public class SimpleInterfaceAccessTest {
+  public static void main(String[] args) {
+    // It is not possible to merge the interface SimpleInterface into SimpleInterfaceImpl, since
+    // this would lead to an illegal class access here.
+    SimpleInterface obj = SimpleInterfaceImplRetriever.getSimpleInterfaceImpl();
+    obj.foo();
+  }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 814e889..da6ef55 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,9 +7,15 @@
 -keep public class classmerging.Test {
   public static void main(...);
 }
+-keep public class classmerging.ConflictingInterfaceSignaturesTest {
+  public static void main(...);
+}
 -keep public class classmerging.ExceptionTest {
   public static void main(...);
 }
+-keep public class classmerging.SimpleInterfaceAccessTest {
+  public static void main(...);
+}
 -keep public class classmerging.TemplateMethodTest {
   public static void main(...);
 }
diff --git a/src/test/examples/classmerging/pkg/SimpleInterfaceImplRetriever.java b/src/test/examples/classmerging/pkg/SimpleInterfaceImplRetriever.java
new file mode 100644
index 0000000..5cfa11e
--- /dev/null
+++ b/src/test/examples/classmerging/pkg/SimpleInterfaceImplRetriever.java
@@ -0,0 +1,25 @@
+// 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 classmerging.pkg;
+
+import classmerging.SimpleInterface;
+
+public class SimpleInterfaceImplRetriever {
+
+  public static SimpleInterface getSimpleInterfaceImpl() {
+    return new SimpleInterfaceImpl();
+  }
+
+  // This class is intentionally marked private. It is not possible to merge the interface
+  // SimpleInterface into SimpleInterfaceImpl, since this would lead to an illegal class access
+  // in SimpleInterfaceAccessTest.
+  private static class SimpleInterfaceImpl implements SimpleInterface {
+
+    @Override
+    public void foo() {
+      System.out.println("In foo on SimpleInterfaceImpl");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 0977533..cf356df 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -26,12 +26,14 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class ClassMergingTest extends TestBase {
@@ -108,6 +110,23 @@
     assertTrue(inspector.clazz("classmerging.SubClassThatReferencesSuperMethod").isPresent());
   }
 
+  @Test
+  public void testConflictingInterfaceSignatures() throws Exception {
+    String main = "classmerging.ConflictingInterfaceSignaturesTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("ConflictingInterfaceSignaturesTest.class"),
+          CF_DIR.resolve("ConflictingInterfaceSignaturesTest$A.class"),
+          CF_DIR.resolve("ConflictingInterfaceSignaturesTest$B.class"),
+          CF_DIR.resolve("ConflictingInterfaceSignaturesTest$InterfaceImpl.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.ConflictingInterfaceSignaturesTest",
+            "classmerging.ConflictingInterfaceSignaturesTest$InterfaceImpl");
+    runTest(main, programFiles, preservedClassNames);
+  }
+
   // If an exception class A is merged into another exception class B, then all exception tables
   // should be updated, and class A should be removed entirely.
   @Test
@@ -146,6 +165,40 @@
     assertEquals(2, numberOfMoveExceptionInstructions);
   }
 
+  @Ignore("b/73958515")
+  @Test
+  public void testNoIllegalClassAccess() throws Exception {
+    String main = "classmerging.SimpleInterfaceAccessTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("SimpleInterface.class"),
+          CF_DIR.resolve("SimpleInterfaceAccessTest.class"),
+          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
+          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class")
+        };
+    // SimpleInterface cannot be merged into SimpleInterfaceImpl because SimpleInterfaceImpl
+    // is in a different package and is not public.
+    ImmutableSet<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.SimpleInterface",
+            "classmerging.SimpleInterfaceAccessTest",
+            "classmerging.pkg.SimpleInterfaceImplRetriever",
+            "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
+    runTest(main, programFiles, preservedClassNames);
+    // If access modifications are allowed then SimpleInterface should be merged into
+    // SimpleInterfaceImpl.
+    preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.SimpleInterfaceAccessTest",
+            "classmerging.pkg.SimpleInterfaceImplRetriever",
+            "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
+    runTest(
+        main,
+        programFiles,
+        preservedClassNames,
+        getProguardConfig(EXAMPLE_KEEP, "-allowaccessmodification"));
+  }
+
   @Test
   public void testTemplateMethodPattern() throws Exception {
     String main = "classmerging.TemplateMethodTest";
@@ -163,8 +216,14 @@
 
   private DexInspector runTest(String main, Path[] programFiles, Set<String> preservedClassNames)
       throws Exception {
+    return runTest(main, programFiles, preservedClassNames, getProguardConfig(EXAMPLE_KEEP, null));
+  }
+
+  private DexInspector runTest(
+      String main, Path[] programFiles, Set<String> preservedClassNames, String proguardConfig)
+      throws Exception {
     AndroidApp input = readProgramFiles(programFiles);
-    AndroidApp output = compileWithR8(input, EXAMPLE_KEEP, this::configure);
+    AndroidApp output = compileWithR8(input, proguardConfig, this::configure);
     DexInspector inspector = new DexInspector(output);
     // Check that all classes in [preservedClassNames] are in fact preserved.
     for (String className : preservedClassNames) {
@@ -181,4 +240,16 @@
     assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
     return inspector;
   }
+
+  private String getProguardConfig(Path path, String additionalRules) throws IOException {
+    StringBuilder builder = new StringBuilder();
+    for (String line : Files.readAllLines(path)) {
+      builder.append(line);
+      builder.append(System.lineSeparator());
+    }
+    if (additionalRules != null) {
+      builder.append(additionalRules);
+    }
+    return builder.toString();
+  }
 }