Test for default methods via both the class and interface hierarchy.

Bug: 144085169
Change-Id: I41829906387bac4b672479631f55efacdb9187ff
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
new file mode 100644
index 0000000..ae678cd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -0,0 +1,181 @@
+// 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.resolution.interfacediamonds;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.SingleTargetLookupTest;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.util.ASMifier;
+
+@RunWith(Parameterized.class)
+public class TwoDefaultMethodsWithoutTopTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public TwoDefaultMethodsWithoutTopTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final List<Class<?>> CLASSES =
+      ImmutableList.of(I.class, J.class, A.class, Main.class);
+
+  @Test
+  public void testResolution() throws Exception {
+    // The resolution is runtime independent, so just run it on the default CF VM.
+    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    AppInfoWithLiveness appInfo =
+        SingleTargetLookupTest.createAppInfoWithLiveness(
+            buildClasses(CLASSES, Collections.emptyList())
+                .addClassProgramData(Collections.singletonList(DumpB.dump()))
+                .build(),
+            Main.class);
+    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
+    assertEquals(2, resolutionTargets.size());
+    assertTrue(
+        resolutionTargets.stream()
+            .anyMatch(m -> m.method.holder.toSourceString().equals(I.class.getTypeName())));
+    assertTrue(
+        resolutionTargets.stream()
+            .anyMatch(m -> m.method.holder.toSourceString().equals(J.class.getTypeName())));
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(CLASSES)
+        .addProgramClassFileData(DumpB.dump())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(r -> checkResult(r, false));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .addProgramClassFileData(DumpB.dump())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(r -> checkResult(r, true));
+  }
+
+  private void checkResult(TestRunResult<?> runResult, boolean isR8) {
+    // TODO(b/144085169): JDK 11 execution produces a different error condition on the R8 output?
+    if (isR8
+        && parameters.getRuntime().isCf()
+        && parameters.getRuntime().asCf().getVm() == CfVm.JDK11) {
+      runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+    } else if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+      if (isR8) {
+        // TODO(b/144085169): Maybe R8 introduces another error due to removal of targets?
+        runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+      } else {
+        // TODO(b/72208584): Desugare changes error result.
+        runResult.assertSuccessWithOutputLines("I::f");
+      }
+    } else {
+      runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
+    }
+  }
+
+  public interface I {
+    default void f() {
+      System.out.println("I::f");
+    }
+  }
+
+  public interface J {
+    default void f() {
+      System.out.println("J::f");
+    }
+  }
+
+  public static class A implements I {}
+
+  public static class B extends A /* implements J via ASM */ {
+    // Intentionally empty.
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new B().f();
+    }
+  }
+
+  private static class DumpB implements Opcodes {
+
+    public static void main(String[] args) throws Exception {
+      ASMifier.main(
+          new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
+    }
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
+          null,
+          DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+          new String[] {
+            // Manually added 'implements J'.
+            DescriptorUtils.getBinaryNameFromJavaType(J.class.getTypeName()),
+          });
+
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}