Add tests for definitions of default methods on program and library

Bug: b/214382176
Change-Id: Ia4743f269b0110894fce9c95dbe01ae944af5f17
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index ae8a263..c4d8683 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -16,6 +16,7 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
 public abstract class TestBuilder<RR extends TestRunResult<RR>, T extends TestBuilder<RR, T>> {
 
@@ -154,6 +155,14 @@
     return addLibraryFiles(Arrays.asList(files));
   }
 
+  public T addLibraryClassFileData(byte[]... classes) {
+    return addLibraryClassFileData(Arrays.asList(classes));
+  }
+
+  public T addLibraryClassFileData(Collection<byte[]> classes) {
+    return addByteCollectionToJar("library.jar", classes, this::addLibraryFiles);
+  }
+
   public T addDefaultRuntimeLibrary(TestParameters parameters) {
     if (parameters.getBackend() == Backend.DEX) {
       addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel()));
@@ -181,9 +190,14 @@
   }
 
   public T addClasspathClassFileData(Collection<byte[]> classes) {
+    return addByteCollectionToJar("cp.jar", classes, this::addClasspathFiles);
+  }
+
+  private T addByteCollectionToJar(
+      String name, Collection<byte[]> classes, Function<Path, T> outputConsumer) {
     Path out;
     try {
-      out = getState().getNewTempFolder().resolve("cp.jar");
+      out = getState().getNewTempFolder().resolve(name);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
@@ -192,7 +206,7 @@
       consumer.accept(ByteDataView.of(bytes), TestBase.extractClassDescriptor(bytes), null);
     }
     consumer.finished(null);
-    return addClasspathFiles(out);
+    return outputConsumer.apply(out);
   }
 
   public final T addTestingAnnotationsAsProgramClasses() {
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index a476d13..719dbd8 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ObjectArrays;
 import java.io.File;
 import java.io.IOException;
@@ -286,7 +287,8 @@
     try {
       AndroidApp.Builder appBuilder = AndroidApp.builder();
       for (byte[] clazz : classes) {
-        appBuilder.addClassProgramData(clazz, Origin.unknown());
+        appBuilder.addClassProgramData(
+            clazz, Origin.unknown(), ImmutableSet.of(TestBase.extractClassDescriptor(clazz)));
       }
       Path path = state.getNewTempFolder().resolve("runtime-classes.jar");
       appBuilder.build().writeToZip(path, OutputMode.ClassFile);
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index cb24356..48e94f0 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -232,6 +232,10 @@
     return withApiLevelsEndingAtExcluding(AndroidApiLevel.L);
   }
 
+  public TestParametersBuilder apiLevelWithDefaultMethodsSupport() {
+    return withApiLevelsStartingAtIncluding(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
+  }
+
   public TestParametersBuilder withCustomRuntime(TestRuntime runtime) {
     assert getUnfilteredAvailableRuntimes().noneMatch(r -> r == runtime);
     customRuntimes.add(runtime);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java
new file mode 100644
index 0000000..d0c2f5c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java
@@ -0,0 +1,231 @@
+// Copyright (c) 2022, 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.interfacetargets;
+
+import static com.android.tools.r8.resolution.interfacetargets.ProgramAndLibraryDefinitionTest.ClassTestParam.DEFINED_WITH_METHOD;
+import static com.android.tools.r8.resolution.interfacetargets.ProgramAndLibraryDefinitionTest.ClassTestParam.NOT_DEFINED;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ProgramAndLibraryDefinitionTest extends TestBase {
+
+  public enum ClassTestParam {
+    NOT_DEFINED,
+    DEFINED_NO_METHOD,
+    DEFINED_WITH_METHOD
+  }
+
+  private final TestParameters parameters;
+  private final ClassTestParam aInLibrary;
+  private final ClassTestParam bInLibrary;
+  private final ClassTestParam aInProgram;
+  private final ClassTestParam bInProgram;
+
+  @Parameters(name = "{0}, aInLibrary: {1}, bInLibrary: {2}, aInProgram: {3}, bInProgram: {4}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDefaultDexRuntime()
+            .withDefaultCfRuntime()
+            .apiLevelWithDefaultMethodsSupport()
+            .build(),
+        ClassTestParam.values(),
+        ClassTestParam.values(),
+        ClassTestParam.values(),
+        ClassTestParam.values());
+  }
+
+  public ProgramAndLibraryDefinitionTest(
+      TestParameters parameters,
+      ClassTestParam aInLibrary,
+      ClassTestParam bInLibrary,
+      ClassTestParam aInProgram,
+      ClassTestParam bInProgram) {
+    this.parameters = parameters;
+    this.aInLibrary = aInLibrary;
+    this.bInLibrary = bInLibrary;
+    this.aInProgram = aInProgram;
+    this.bInProgram = bInProgram;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8FullTestBuilder testBuilder =
+        testForR8(parameters.getBackend())
+            .addDefaultRuntimeLibrary(parameters)
+            .addProgramClasses(Main.class, Implementer.class, I.class)
+            .setMinApi(parameters.getApiLevel())
+            .addDontWarn(A.class, B.class)
+            .addKeepMainRule(Main.class)
+            .noMinification()
+            .allowUnusedDontWarnPatterns();
+    byte[] libraryA = getAFromClassTestParam(this.aInLibrary);
+    addIfNotNull(libraryA, testBuilder::addLibraryClassFileData);
+    byte[] libraryB = getBFromClassTestParam(bInLibrary);
+    addIfNotNull(libraryB, testBuilder::addLibraryClassFileData);
+    addIfNotNull(getAFromClassTestParam(aInProgram), testBuilder::addProgramClassFileData);
+    addIfNotNull(getBFromClassTestParam(bInProgram), testBuilder::addProgramClassFileData);
+    R8TestCompileResult compileResult = testBuilder.compile();
+    if (libraryA != null) {
+      compileResult.addRunClasspathClassFileData(libraryA);
+    }
+    if (libraryB != null) {
+      compileResult.addRunClasspathClassFileData(libraryB);
+    }
+    R8TestRunResult runResult = compileResult.run(parameters.getRuntime(), Main.class);
+    if (isExpectedToFailWithNoClassDefError()) {
+      runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+    } else if (isExpectedToFailWithNoSuchMethodError()) {
+      runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+    } else if (isExpectedToFailWithICCE()) {
+      runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+    } else if (isDefinedOnAProgram() || (isDefinedOnALibrary() && !isAInProgram())) {
+      runResult.assertSuccessWithOutputLines("A::foo");
+    } else {
+      assert isDefinedOnBProgram() || (isDefinedOnBLibrary() && !isBInProgram());
+      runResult.assertSuccessWithOutputLines("B::foo");
+    }
+  }
+
+  private boolean isAInProgram() {
+    return aInProgram != NOT_DEFINED;
+  }
+
+  private boolean isBInProgram() {
+    return bInProgram != NOT_DEFINED;
+  }
+
+  private boolean isAInLibrary() {
+    return aInLibrary != NOT_DEFINED;
+  }
+
+  private boolean isBInLibrary() {
+    return bInLibrary != NOT_DEFINED;
+  }
+
+  private boolean isDefinedOnAProgram() {
+    return aInProgram == DEFINED_WITH_METHOD;
+  }
+
+  private boolean isDefinedOnBProgram() {
+    return bInProgram == DEFINED_WITH_METHOD;
+  }
+
+  private boolean isDefinedOnALibrary() {
+    return aInLibrary == DEFINED_WITH_METHOD;
+  }
+
+  private boolean isDefinedOnBLibrary() {
+    return bInLibrary == DEFINED_WITH_METHOD;
+  }
+
+  private boolean isExpectedToFailWithNoClassDefError() {
+    return (!isAInLibrary() && !isAInProgram()) || (!isBInLibrary() && !isBInProgram());
+  }
+
+  private boolean isExpectedToFailWithNoSuchMethodError() {
+    boolean notDefinedInProgram = !isDefinedOnAProgram() && !isDefinedOnBProgram();
+    boolean notDefinedInLibrary = !isDefinedOnALibrary() && !isDefinedOnBLibrary();
+    if (notDefinedInLibrary && notDefinedInProgram) {
+      return true;
+    }
+    if (notDefinedInProgram) {
+      // TODO(b/214382176): Currently, a program definition will shadow the library definition and
+      //  R8 will optimize the interfaces away.
+      if (isDefinedOnALibrary() && isDefinedOnBLibrary()) {
+        return isAInProgram() && isBInProgram();
+      } else if (isDefinedOnALibrary()) {
+        return isAInProgram();
+      } else {
+        assert isDefinedOnBLibrary();
+        return isBInProgram();
+      }
+    }
+    return false;
+  }
+
+  private boolean isExpectedToFailWithICCE() {
+    if (isDefinedOnAProgram() && isDefinedOnBProgram()) {
+      return true;
+    }
+    if (!isAInProgram() && !isBInProgram()) {
+      return isDefinedOnALibrary() && isDefinedOnBLibrary();
+    }
+    if (isDefinedOnALibrary() && !isAInProgram() && isDefinedOnBProgram()) {
+      return true;
+    }
+    if (isDefinedOnBLibrary() && !isBInProgram() && isDefinedOnAProgram()) {
+      return true;
+    }
+    return false;
+  }
+
+  private void addIfNotNull(byte[] clazz, Consumer<byte[]> consumer) {
+    if (clazz != null) {
+      consumer.accept(clazz);
+    }
+  }
+
+  private byte[] getAFromClassTestParam(ClassTestParam param) throws Exception {
+    switch (param) {
+      case NOT_DEFINED:
+        return null;
+      case DEFINED_NO_METHOD:
+        return transformer(A.class).removeMethods(MethodPredicate.onName("foo")).transform();
+      default:
+        assert param == DEFINED_WITH_METHOD;
+        return transformer(A.class).transform();
+    }
+  }
+
+  private byte[] getBFromClassTestParam(ClassTestParam param) throws Exception {
+    switch (param) {
+      case NOT_DEFINED:
+        return null;
+      case DEFINED_NO_METHOD:
+        return transformer(B.class).removeMethods(MethodPredicate.onName("bar")).transform();
+      default:
+        assert param == DEFINED_WITH_METHOD;
+        return transformer(B.class).renameMethod(MethodPredicate.onName("bar"), "foo").transform();
+    }
+  }
+
+  public interface A {
+
+    default void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  public interface B {
+
+    default void /* foo */ bar() { // Will be renamed to foo.
+      System.out.println("B::foo");
+    }
+  }
+
+  public interface I extends A {}
+
+  public static class Implementer implements I, B {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new Implementer().foo();
+    }
+  }
+}