Additional tests for testing invoke-special translation in D8

Bug: 144450911
Change-Id: Idf53939d849dfa08079a5aa14458a3c0dd68bb20
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
new file mode 100644
index 0000000..1765fc2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
@@ -0,0 +1,122 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+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.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeFinalTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeFinalTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCallingFinal()
+      throws IOException, CompilationFailedException, ExecutionException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClasses(Main.class, A.class)
+            .addProgramClassFileData(
+                getClassWithTransformedInvoked(B.class), getClassWithTransformedInvoked(C.class))
+            .run(parameters.getRuntime(), Main.class);
+    // TODO(b/144450911): Remove when fixed.
+    if (parameters.isCfRuntime()) {
+      runResult.assertSuccessWithOutputLines(
+          "Hello from B",
+          "Hello from B",
+          "Hello from B",
+          "Hello from B",
+          "Hello from A",
+          "Hello from B");
+    } else {
+      DexRuntime dexRuntime = parameters.getRuntime().asDex();
+      if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+        runResult.assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+      } else {
+        runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
+      }
+    }
+  }
+
+  private byte[] getClassWithTransformedInvoked(Class<?> clazz) throws IOException {
+    return transformer(clazz)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              // The super call to bar() is already INVOKESPECIAL.
+              assertTrue(name.equals("foo") || opcode == INVOKESPECIAL);
+              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("Hello from A");
+    }
+
+    public void bar() {
+      // TODO(b/110175213): We cannot change this to an invoke-special since this requires a
+      //  direct bridge in DEX.
+      foo();
+    }
+  }
+
+  public static class B extends A {
+
+    // Having a final method allows us to rewrite invoke-special foo() to invoke-virtual foo().
+    public final void foo() {
+      System.out.println("Hello from B");
+    }
+
+    public void bar() {
+      foo();
+      ((A) this).foo();
+      super.bar();
+    }
+  }
+
+  public static class C extends B {
+
+    public void bar() {
+      foo();
+      ((B) this).foo();
+      ((A) this).foo();
+      super.bar();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
new file mode 100644
index 0000000..60a788f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
@@ -0,0 +1,85 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+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.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeSpecialOnSameClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialOnSameClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(Main.class)
+            .addProgramClassFileData(getClassWithTransformedInvoked())
+            .run(parameters.getRuntime(), Main.class);
+    // TODO(b/144450911): Remove when fixed.
+    if (parameters.isCfRuntime()) {
+      runResult.assertSuccessWithOutputLines("Hello World!");
+    } else {
+      DexRuntime dexRuntime = parameters.getRuntime().asDex();
+      if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+        runResult.assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+      } else {
+        runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
+      }
+    }
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(A.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+
+    public void bar() {
+      foo(); // Will be rewritten to invoke-special A.foo()
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().bar();
+    }
+  }
+}