Add a test of a pseudo platform API

Bug: b/229793269
Change-Id: Ib1d00bf180736cfed682263c747e850da83ccfcb
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
new file mode 100644
index 0000000..3c1716a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
@@ -0,0 +1,164 @@
+// 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test of an API which is to be treated as a platform API. This is simulating an API which can be
+// implemented by an OEM in an on-device APK containing the DEX code. The program will probe for the
+// OEM class and if not found it will use a built-in program class implementing the interface.
+//
+// This is tested with and without library desugaring.
+
+// For context see b/229793269.
+@RunWith(Parameterized.class)
+public class PseudoPlatformApiTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntimes()
+            .withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
+            .build());
+  }
+
+  private Path androidJarAdditions() throws Exception {
+    return ZipBuilder.builder(temp.newFile("additions_android.jar").toPath())
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            ToolHelper.getClassFileForTestClass(PseudoPlatformInterface.class))
+        .build();
+  }
+
+  private Path androidJarAdditionsDex() throws Exception {
+    // Build the OEM DEX files without library desugaring enabled and using min API 28.
+    return testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addProgramClasses(PseudoPlatformInterface.class)
+        .setMinApi(AndroidApiLevel.B)
+        .compile()
+        .writeToZip();
+  }
+
+  private Path oemDex() throws Exception {
+    // Build the OEM DEX files without library desugaring enabled and using min API 28.
+    return testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        // This is equivalent to adding the PseudoPlatformInterface to android.jar.
+        .addLibraryClasses(PseudoPlatformInterface.class)
+        .addProgramClasses(OemClass.class)
+        .setMinApi(AndroidApiLevel.P)
+        .compile()
+        .writeToZip();
+  }
+
+  @Test
+  public void testD8WithoutLibraryDesugaringOemClassNotPresent() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addLibraryFiles(androidJarAdditions())
+        .addProgramClasses(ProgramClass.class)
+        .setMinApi(AndroidApiLevel.H_MR2)
+        .addRunClasspathFiles(androidJarAdditionsDex())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT");
+  }
+
+  @Test
+  public void testD8WithoutLibraryDesugaringOemClassPresent() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addLibraryFiles(androidJarAdditions())
+        .addProgramClasses(ProgramClass.class)
+        .setMinApi(AndroidApiLevel.H_MR2)
+        .addRunClasspathFiles(androidJarAdditionsDex())
+        .addRunClasspathFiles(oemDex())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("OEM-X", "Y-OEM");
+  }
+
+  @Test
+  public void testD8WithLibraryDesugaringOemClassNotPresent() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addLibraryFiles(androidJarAdditions())
+        .addProgramClasses(ProgramClass.class)
+        .setMinApi(AndroidApiLevel.H_MR2)
+        // Enable library desugaring with an effective min API of 1.
+        .enableLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+        .addRunClasspathFiles(androidJarAdditionsDex())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT");
+  }
+
+  @Test
+  public void testD8WithLibraryDesugaringOemClassPresent() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addLibraryFiles(androidJarAdditions())
+        .addProgramClasses(ProgramClass.class)
+        .setMinApi(AndroidApiLevel.H_MR2)
+        // Enable library desugaring with an effective min API of 1.
+        .enableLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+        .addRunClasspathFiles(androidJarAdditionsDex())
+        .addRunClasspathFiles(oemDex())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("OEM-X", "Y-OEM");
+  }
+
+  interface PseudoPlatformInterface {
+    Function<String, String> api(Function<String, String> fn);
+  }
+
+  static class OemClass implements PseudoPlatformInterface {
+    public Function<String, String> api(Function<String, String> fn) {
+      System.out.println(fn.apply("OEM-"));
+      return (v) -> v + "-OEM";
+    }
+  }
+
+  static class ProgramClass implements PseudoPlatformInterface {
+    public Function<String, String> api(Function<String, String> fn) {
+      System.out.println(fn.apply("DEFAULT-"));
+      return (v) -> v + "-DEFAULT";
+    }
+
+    public static void main(String[] args) {
+      PseudoPlatformInterface pseudoPlatformInterface = null;
+      try {
+        Class<?> oemClass =
+            Class.forName(
+                "com.android.tools.r8.desugar.desugaredlibrary.PseudoPlatformApiTest$OemClass");
+        pseudoPlatformInterface =
+            (PseudoPlatformInterface) oemClass.getDeclaredConstructor().newInstance();
+      } catch (ClassNotFoundException
+          | NoSuchMethodException
+          | InstantiationException
+          | InvocationTargetException
+          | IllegalAccessException e) {
+        // Could not load OEM class use built-in program class.
+        pseudoPlatformInterface = new ProgramClass();
+      }
+      Function<String, String> fn = (v) -> v + "X";
+      System.out.println(pseudoPlatformInterface.api(fn).apply("Y"));
+    }
+  }
+}