Add test for invoke-virtual problems when using applymapping

Additionally, this test also includes a new CompileMemoization
interface that is useful for memoizing compilation results.

Bug: 128868424
Change-Id: I19fd5733c6c1d63a2addc195052af7071698018e
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index bd57729..8839960 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static com.google.common.collect.Lists.cartesianProduct;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
@@ -69,6 +69,8 @@
 import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
 import org.objectweb.asm.ClassReader;
@@ -161,6 +163,23 @@
   @Rule
   public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
 
+  private static TemporaryFolder staticTemp = null;
+
+  @BeforeClass
+  public static void testBaseBeforeClassSetup() {
+    assert staticTemp == null;
+    staticTemp = ToolHelper.getTemporaryFolderForTest();
+  }
+
+  @AfterClass
+  public static void testBaseBeforeClassTearDown() {
+    staticTemp = null;
+  }
+
+  public static TemporaryFolder getStaticTemp() {
+    return staticTemp;
+  }
+
   public static TestParametersBuilder getTestParameters() {
     return TestParametersBuilder.builder();
   }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
new file mode 100644
index 0000000..7989a29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -0,0 +1,151 @@
+// 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.naming.applymapping;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.LibraryBase;
+import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.LibrarySubclass;
+import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.ProgramClass;
+import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.ProgramSubclass;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApplyMappingVirtualInvokeTest extends TestBase {
+
+  public static final String EXPECTED_PROGRAM = StringUtils.lines("LibraryBase.foo");
+  public static final String EXPECTED_PROGRAM_SUBCLASS =
+      StringUtils.lines("ProgramSubclass.foo", "LibraryBase.foo");
+
+  public static class TestClasses {
+
+    public static class LibraryBase {
+
+      void foo() {
+        System.out.println("LibraryBase.foo");
+      }
+    }
+
+    public static class LibrarySubclass extends LibraryBase {}
+
+    public static class ProgramClass {
+
+      public static void main(String[] args) {
+        new LibrarySubclass().foo();
+      }
+    }
+
+    public static class ProgramSubclass extends LibrarySubclass {
+
+      public static void main(String[] args) {
+        new ProgramSubclass().foo();
+      }
+
+      @Override
+      void foo() {
+        System.out.println("ProgramSubclass.foo");
+        super.foo();
+      }
+    }
+  }
+
+  private static final Class<?>[] LIBRARY_CLASSES = {LibraryBase.class, LibrarySubclass.class};
+  private static final Class<?>[] PROGRAM_CLASSES = {ProgramClass.class, ProgramSubclass.class};
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public ApplyMappingVirtualInvokeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Function<Backend, R8TestCompileResult> compilationResults =
+      memoizeFunction(ApplyMappingVirtualInvokeTest::compile);
+
+  private static R8TestCompileResult compile(Backend backend) throws CompilationFailedException {
+    return testForR8(getStaticTemp(), backend)
+        .addProgramClasses(LIBRARY_CLASSES)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(LIBRARY_CLASSES)
+        .addOptionsModification(
+            options -> {
+              options.enableInlining = false;
+              options.enableVerticalClassMerging = false;
+              options.enableHorizontalClassMerging = false;
+              options.enableClassInlining = false;
+            })
+        .compile();
+  }
+
+  @Test
+  public void runJvmProgramTest()
+      throws ExecutionException, CompilationFailedException, IOException {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(LIBRARY_CLASSES)
+        .addProgramClasses(PROGRAM_CLASSES)
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutput(EXPECTED_PROGRAM);
+  }
+
+  @Test
+  public void runJvmProgramSubclassTest()
+      throws ExecutionException, CompilationFailedException, IOException {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(LIBRARY_CLASSES)
+        .addProgramClasses(PROGRAM_CLASSES)
+        .run(parameters.getRuntime(), ProgramSubclass.class)
+        .assertSuccessWithOutput(EXPECTED_PROGRAM_SUBCLASS);
+  }
+
+  @Ignore("b/128868424")
+  @Test
+  public void testProgramClass()
+      throws ExecutionException, CompilationFailedException, IOException {
+    runTest(ProgramClass.class, EXPECTED_PROGRAM);
+  }
+
+  @Ignore("b/128868424")
+  @Test
+  public void testProgramSubClass()
+      throws ExecutionException, IOException, CompilationFailedException {
+    runTest(ProgramSubclass.class, EXPECTED_PROGRAM_SUBCLASS);
+  }
+
+  private void runTest(Class<?> main, String expected)
+      throws ExecutionException, IOException, CompilationFailedException {
+    R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters.getBackend());
+    Path outPath = temp.newFile("out.zip").toPath();
+    libraryCompileResult.writeToZip(outPath);
+    testForR8(parameters.getBackend())
+        .noTreeShaking()
+        .noMinification()
+        .addProgramClasses(PROGRAM_CLASSES)
+        .addApplyMapping(libraryCompileResult.getProguardMap())
+        .addLibraryClasses(LIBRARY_CLASSES)
+        .addLibraryFiles(runtimeJar(parameters.getBackend()))
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .addRunClasspathFiles(outPath)
+        .run(parameters.getRuntime(), main)
+        .assertSuccessWithOutput(expected);
+  }
+}