Test wrapper merging on all VMs and API levels.

This CL also changes the test with guards to ensure the code reflects
the assumptions for wrapper guards in application code.

Change-Id: I08dba2350a9854f46f0cf13052a2c8e2e6a7127f
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 8bc4820..18571d0 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ObjectArrays;
@@ -150,4 +151,9 @@
   public JvmTestBuilder addVmArguments(String... arguments) {
     return addVmArguments(Arrays.asList(arguments));
   }
+
+  public JvmTestBuilder addAndroidBuildVersion() {
+    addVmArguments("-D" + AndroidBuildVersion.PROPERTY + "=10000");
+    return addProgramClasses(AndroidBuildVersion.class);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 39356e1..0816496 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -243,6 +243,11 @@
     return self();
   }
 
+  public CR setSystemProperty(String name, String value) {
+    vmArguments.add("-D" + name + "=" + value);
+    return self();
+  }
+
   public Path writeToZip() throws IOException {
     Path file = state.getNewTempFolder().resolve("out.zip");
     writeToZip(file);
@@ -429,6 +434,13 @@
         withArt6Plus64BitsLib && vm.getVersion().isAtLeast(DexVm.Version.V6_0_1)
             ? builder -> builder.appendArtOption("--64")
             : builder -> {};
+    commandConsumer =
+        commandConsumer.andThen(
+            builder -> {
+              for (String vmArgument : vmArguments) {
+                builder.appendArtOption(vmArgument);
+              }
+            });
     ProcessResult result =
         ToolHelper.runArtRaw(
             classPath, mainClass, commandConsumer, vm, withArtFrameworks, arguments);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index b7869e4..490cf54 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -62,6 +63,14 @@
   private PrintStream oldStderr = null;
   protected OutputMode outputMode = OutputMode.DexIndexed;
 
+  private boolean isAndroidBuildVersionAdded = false;
+
+  public T addAndroidBuildVersion() {
+    addProgramClasses(AndroidBuildVersion.class);
+    isAndroidBuildVersionAdded = true;
+    return self();
+  }
+
   TestCompilerBuilder(TestState state, B builder, Backend backend) {
     super(state, builder);
     this.backend = backend;
@@ -133,6 +142,9 @@
       cr =
           internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build))
               .addRunClasspathFiles(additionalRunClassPath);
+      if (isAndroidBuildVersionAdded) {
+        cr.setSystemProperty(AndroidBuildVersion.PROPERTY, "" + builder.getMinApiLevel());
+      }
       return cr;
     } finally {
       if (stdout != null) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
index 4c1caf7..9a29671 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
@@ -5,66 +5,155 @@
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
 import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.Set;
 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 WrapperMergeTest extends DesugaredLibraryTestBase {
 
+  private static final String EXPECTED = StringUtils.lines("[1, 2, 3]", "[2, 3, 4]");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public WrapperMergeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addAndroidBuildVersion()
+        .addProgramClassesAndInnerClasses(MyArrays1.class)
+        .addProgramClassesAndInnerClasses(MyArrays2.class)
+        .addProgramClasses(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
   @Test
   public void testWrapperMerge() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
     // Multiple wrapper classes have to be merged here.
-    Path path1 = testForD8()
-        .addProgramClasses(Executor1.class)
-        .setMinApi(AndroidApiLevel.B)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-        .compile()
-        .inspect(this::assertWrappers)
-        .writeToZip();
-    Path path2 = testForD8()
-        .addProgramClasses(Executor2.class)
-        .setMinApi(AndroidApiLevel.B)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-        .compile()
-        .inspect(this::assertWrappers)
-        .writeToZip();
+    Path path1 = compileWithCoreLibraryDesugaring(MyArrays1.class);
+    Path path2 = compileWithCoreLibraryDesugaring(MyArrays2.class);
     testForD8()
         .addProgramFiles(path1, path2)
+        .addProgramClasses(TestClass.class)
+        .addAndroidBuildVersion()
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .setMinApi(parameters.getApiLevel())
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor1.class)
-        .assertSuccessWithOutput(StringUtils.lines("[1, 2, 3]"));
+        .inspect(this::assertWrappers)
+        .inspect(this::assertNoDuplicates)
+        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private Path compileWithCoreLibraryDesugaring(Class<?> clazz) throws Exception {
+    return testForD8()
+        .addProgramClassesAndInnerClasses(clazz)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertWrappers)
+        .writeToZip();
+  }
+
+  private void assertNoDuplicates(CodeInspector inspector) {
+    Object2ReferenceMap<String, Set<FoundClassSubject>> map = new Object2ReferenceOpenHashMap<>();
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      map.computeIfAbsent(clazz.getFinalName(), k -> Sets.newIdentityHashSet()).add(clazz);
+    }
+    for (Set<FoundClassSubject> duplicates : map.values()) {
+      if (duplicates.size() > 1) {
+        fail("Unexpected duplicates: " + duplicates);
+      }
+    }
+  }
+
+  private boolean hasNativeIntUnaryOperator() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
   }
 
   private void assertWrappers(CodeInspector inspector) {
-    assertEquals(2,inspector.allClasses().stream().filter(c -> c.getOriginalName().contains(
-        DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)).count());
+    assertEquals(
+        hasNativeIntUnaryOperator() ? 0 : 2,
+        inspector.allClasses().stream()
+            .filter(
+                c ->
+                    c.getOriginalName().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX))
+            .count());
   }
 
-  static class Executor1 {
+  static class MyArrays1 {
 
-    public static void main(String[] args) {
-      int[] ints = new int[3];
-      Arrays.setAll(ints,x->x+1);
-      System.out.println(Arrays.toString(ints));
+    interface IntGenerator {
+      int generate(int index);
+    }
+
+    public static void setAll(int[] ints, IntGenerator generator) {
+      if (AndroidBuildVersion.VERSION >= 24) {
+        java.util.Arrays.setAll(ints, generator::generate);
+      } else {
+        for (int i = 0; i < ints.length; i++) {
+          ints[i] = generator.generate(i);
+        }
+      }
     }
   }
 
-  static class Executor2 {
+  static class MyArrays2 {
 
-    public static void main(String[] args) {
-      int[] ints = new int[3];
-      Arrays.setAll(ints,x->x+2);
-      System.out.println(Arrays.toString(ints));
+    interface IntGenerator {
+      int generate(int index);
+    }
+
+    public static void setAll(int[] ints, IntGenerator generator) {
+      if (AndroidBuildVersion.VERSION >= 24) {
+        java.util.Arrays.setAll(ints, generator::generate);
+      } else {
+        for (int i = 0; i < ints.length; i++) {
+          ints[i] = generator.generate(i);
+        }
+      }
     }
   }
 
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      int[] ints = new int[3];
+      MyArrays1.setAll(ints, x -> x + 1);
+      System.out.println(Arrays.toString(ints));
+      MyArrays2.setAll(ints, x -> x + 2);
+      System.out.println(Arrays.toString(ints));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
new file mode 100644
index 0000000..ab3ea7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
@@ -0,0 +1,14 @@
+// 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.testing;
+
+/**
+ * Stub class to simulate having a Build.VERSION property in headless tests.
+ *
+ * <p>Use test builder addAndroidBuildVersion() methods when used in tests.
+ */
+public class AndroidBuildVersion {
+  public static final String PROPERTY = "com.android.tools.r8.testing.AndroidBuildVersion.VERSION";
+  public static int VERSION = Integer.parseInt(System.getProperty(PROPERTY));
+}