Move testClassesHaveBeenMerged() to ClassesHaveBeenMergedTest

Change-Id: Ibc0c9bdcc225e27584cbdc746bed32ed7c85a174
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 6bf8757..59c7b8e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.Predicates;
@@ -398,6 +399,10 @@
     return appInfo.options();
   }
 
+  public TestingOptions testing() {
+    return options().testing;
+  }
+
   public RootSet rootSet() {
     return rootSet;
   }
@@ -466,6 +471,9 @@
   public void setVerticallyMergedClasses(VerticallyMergedClasses verticallyMergedClasses) {
     assert this.verticallyMergedClasses == null;
     this.verticallyMergedClasses = verticallyMergedClasses;
+    if (testing().verticallyMergedClassesConsumer != null) {
+      testing().verticallyMergedClassesConsumer.accept(dexItemFactory(), verticallyMergedClasses);
+    }
   }
 
   public EnumValueInfoMapCollection unboxedEnums() {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 1633feb..eb2d165 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessingId;
@@ -1222,6 +1223,9 @@
                 new DefaultRepackagingConfiguration(
                     appView.dexItemFactory(), appView.options().getProguardConfiguration());
 
+    public BiConsumer<DexItemFactory, VerticallyMergedClasses> verticallyMergedClassesConsumer =
+        null;
+
     public Consumer<Deque<SortedProgramMethodSet>> waveModifier = waves -> {};
 
     /**
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d7bebd8..a92fefd 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1648,6 +1648,10 @@
     return path;
   }
 
+  public static DexType toDexType(Class<?> clazz, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createType(descriptor(clazz));
+  }
+
   public static String binaryName(Class<?> clazz) {
     return DescriptorUtils.getBinaryNameFromJavaType(typeName(clazz));
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 3e5ba5f..1fa6604 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -26,6 +28,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -103,6 +106,12 @@
         });
   }
 
+  public T addVerticallyMergedClassesInspector(
+      BiConsumer<DexItemFactory, VerticallyMergedClasses> inspector) {
+    return addOptionsModification(
+        options -> options.testing.verticallyMergedClassesConsumer = inspector);
+  }
+
   public CR compile() throws CompilationFailedException {
     AndroidAppConsumers sink = new AndroidAppConsumers();
     builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
new file mode 100644
index 0000000..8577fae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
@@ -0,0 +1,186 @@
+// Copyright (c) 2020, 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.classmerging.vertical;
+
+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.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassesHaveBeenMergedTest extends VerticalClassMergerTestBase {
+
+  public ClassesHaveBeenMergedTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void testClassesHaveBeenMerged() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassesHaveBeenMergedTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  private void inspectVerticallyMergedClasses(
+      DexItemFactory dexItemFactory, VerticallyMergedClasses verticallyMergedClasses) {
+    assertMergedIntoSubtype(GenericInterface.class, dexItemFactory, verticallyMergedClasses);
+    assertMergedIntoSubtype(GenericAbstractClass.class, dexItemFactory, verticallyMergedClasses);
+    assertMergedIntoSubtype(Outer.SuperClass.class, dexItemFactory, verticallyMergedClasses);
+    assertMergedIntoSubtype(SuperClass.class, dexItemFactory, verticallyMergedClasses);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(GenericInterfaceImpl.class), isPresent());
+    assertThat(inspector.clazz(Outer.SubClass.class), isPresent());
+    assertThat(inspector.clazz(SubClass.class), isPresent());
+    assertThat(inspector.clazz(GenericInterface.class), not(isPresent()));
+    assertThat(inspector.clazz(GenericAbstractClass.class), not(isPresent()));
+    assertThat(inspector.clazz(Outer.SuperClass.class), not(isPresent()));
+    assertThat(inspector.clazz(SuperClass.class), not(isPresent()));
+  }
+
+  public static class TestClass {
+
+    public static void main(String... args) {
+      GenericInterface<?> iface = new GenericInterfaceImpl();
+      callMethodOnIface(iface);
+      GenericAbstractClass<?> clazz = new GenericAbstractClassImpl();
+      callMethodOnAbstractClass(clazz);
+      Outer outer = new Outer();
+      Outer.SubClass inner = outer.getInstance();
+      System.out.println(outer.getInstance().method());
+      System.out.println(new SubClass(42));
+
+      // Ensure that the instantiations are not dead code eliminated.
+      escape(clazz);
+      escape(iface);
+      escape(inner);
+      escape(outer);
+    }
+
+    private static void callMethodOnIface(GenericInterface<?> iface) {
+      System.out.println(iface.method());
+    }
+
+    private static void callMethodOnAbstractClass(GenericAbstractClass<?> clazz) {
+      System.out.println(clazz.method());
+      System.out.println(clazz.otherMethod());
+    }
+
+    @NeverInline
+    static void escape(Object o) {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(o);
+      }
+    }
+  }
+
+  public abstract static class GenericAbstractClass<T> {
+
+    public abstract T method();
+
+    public T otherMethod() {
+      return null;
+    }
+  }
+
+  public static class GenericAbstractClassImpl extends GenericAbstractClass<String> {
+
+    @Override
+    public String method() {
+      return "Hello from GenericAbstractClassImpl";
+    }
+
+    @Override
+    public String otherMethod() {
+      return "otherMethod";
+    }
+  }
+
+  public interface GenericInterface<T> {
+
+    T method();
+  }
+
+  @NoHorizontalClassMerging
+  public static class GenericInterfaceImpl implements GenericInterface<String> {
+
+    @Override
+    public String method() {
+      return "method";
+    }
+  }
+
+  public static class SuperClass {
+
+    private final int field;
+
+    public SuperClass(int field) {
+      this.field = field;
+    }
+
+    public int getField() {
+      return field;
+    }
+  }
+
+  public static class SubClass extends SuperClass {
+
+    private int field;
+
+    public SubClass(int field) {
+      this(field, field + 100);
+    }
+
+    public SubClass(int one, int other) {
+      super(one);
+      field = other;
+    }
+
+    public String toString() {
+      return "is " + field + " " + getField();
+    }
+  }
+
+  static class Outer {
+
+    /**
+     * This class is package private to trigger the generation of bridge methods for the visibility
+     * change of methods from public subtypes.
+     */
+    static class SuperClass {
+
+      public String method() {
+        return "Method in SuperClass.";
+      }
+    }
+
+    @NoHorizontalClassMerging
+    public static class SubClass extends SuperClass {
+      // Intentionally left empty.
+    }
+
+    public SubClass getInstance() {
+      return new SubClass();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index e831c02..37e4bc6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -57,7 +57,6 @@
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.IntStream;
@@ -101,7 +100,7 @@
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
-      throws IOException, ExecutionException, CompilationFailedException {
+      throws IOException, CompilationFailedException {
     inspector =
         testForR8(parameters.getBackend())
             .addProgramFiles(EXAMPLE_JAR)
@@ -124,19 +123,6 @@
   );
 
   @Test
-  public void testClassesHaveBeenMerged() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
-    runR8(EXAMPLE_KEEP, this::configure);
-    // GenericInterface should be merged into GenericInterfaceImpl.
-    for (String candidate : CAN_BE_MERGED) {
-      assertThat(inspector.clazz(candidate), not(isPresent()));
-    }
-    assertThat(inspector.clazz("classmerging.GenericInterfaceImpl"), isPresent());
-    assertThat(inspector.clazz("classmerging.Outer$SubClass"), isPresent());
-    assertThat(inspector.clazz("classmerging.SubClass"), isPresent());
-  }
-
-  @Test
   public void testClassesHaveNotBeenMerged() throws Throwable {
     runR8(DONT_OPTIMIZE, null);
     for (String candidate : CAN_BE_MERGED) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTestBase.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTestBase.java
new file mode 100644
index 0000000..8aaf61e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTestBase.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2020, 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.classmerging.vertical;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import org.junit.runners.Parameterized.Parameters;
+
+public abstract class VerticalClassMergerTestBase extends TestBase {
+
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public VerticalClassMergerTestBase(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public static void assertMergedIntoSubtype(
+      Class<?> clazz,
+      DexItemFactory dexItemFactory,
+      VerticallyMergedClasses verticallyMergedClasses) {
+    assertTrue(verticallyMergedClasses.hasBeenMergedIntoSubtype(toDexType(clazz, dexItemFactory)));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotation.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
similarity index 78%
rename from src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotation.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
index bb33801..795837e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotation.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
@@ -14,8 +14,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.classmerging.vertical.testclasses.Outer;
-import com.android.tools.r8.classmerging.vertical.testclasses.Outer.Base;
+import com.android.tools.r8.classmerging.vertical.testclasses.VerticalClassMergingWithNonVisibleAnnotationTestClasses;
+import com.android.tools.r8.classmerging.vertical.testclasses.VerticalClassMergingWithNonVisibleAnnotationTestClasses.Base;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -26,7 +26,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class VerticalClassMergingWithNonVisibleAnnotation extends TestBase {
+public class VerticalClassMergingWithNonVisibleAnnotationTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -35,18 +35,20 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public VerticalClassMergingWithNonVisibleAnnotation(TestParameters parameters) {
+  public VerticalClassMergingWithNonVisibleAnnotationTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(Outer.class)
+        .addInnerClasses(VerticalClassMergingWithNonVisibleAnnotationTestClasses.class)
         .addProgramClasses(Sub.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Sub.class)
-        .addKeepClassRules(Outer.class.getPackage().getName() + ".Outer$Private* { *; }")
+        .addKeepClassRules(
+            VerticalClassMergingWithNonVisibleAnnotationTestClasses.class.getTypeName()
+                + "$Private* { *; }")
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
@@ -65,7 +67,8 @@
               assertThat(foo, isPresent());
               AnnotationSubject privateMethodAnnotation =
                   foo.annotation(
-                      Outer.class.getPackage().getName() + ".Outer$PrivateMethodAnnotation");
+                      VerticalClassMergingWithNonVisibleAnnotationTestClasses.class.getTypeName()
+                          + "$PrivateMethodAnnotation");
               assertThat(privateMethodAnnotation, isPresent());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/Outer.java b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/VerticalClassMergingWithNonVisibleAnnotationTestClasses.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/Outer.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/VerticalClassMergingWithNonVisibleAnnotationTestClasses.java
index 46ef8c8..3b8bf5b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/Outer.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/VerticalClassMergingWithNonVisibleAnnotationTestClasses.java
@@ -8,7 +8,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-public class Outer {
+public class VerticalClassMergingWithNonVisibleAnnotationTestClasses {
 
   @Retention(RetentionPolicy.RUNTIME)
   private @interface PrivateClassAnnotation {}