Reproduce Mockito's mock of empty interface after devirtualization.

Bug: 120675359
Change-Id: I77fe93d4af84dbe97870278cf62cbd030d680b7a
diff --git a/src/test/examples/mockito_interface/Implementer.java b/src/test/examples/mockito_interface/Implementer.java
new file mode 100644
index 0000000..40c8fff
--- /dev/null
+++ b/src/test/examples/mockito_interface/Implementer.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, 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 mockito_interface;
+
+import java.util.logging.Logger;
+
+public class Implementer implements Interface {
+  private static final String TAG = Implementer.class.getSimpleName();
+  private Logger logger;
+
+  public Implementer() {
+    this.logger = Logger.getLogger(TAG);
+  }
+
+  @Override
+  public void onEnterForeground() {
+    logger.info("onEnterForeground called");
+  }
+
+}
diff --git a/src/test/examples/mockito_interface/Interface.java b/src/test/examples/mockito_interface/Interface.java
new file mode 100644
index 0000000..235de62
--- /dev/null
+++ b/src/test/examples/mockito_interface/Interface.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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 mockito_interface;
+
+public interface Interface {
+  void onEnterForeground();
+}
diff --git a/src/test/examples/mockito_interface/InterfaceTest.java b/src/test/examples/mockito_interface/InterfaceTest.java
new file mode 100644
index 0000000..2502b13
--- /dev/null
+++ b/src/test/examples/mockito_interface/InterfaceTest.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2018, 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 mockito_interface;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(Parameterized.class)
+public class InterfaceTest {
+  @Mock
+  private Interface fld;
+
+  private InterfaceUser user;
+
+  private boolean flag;
+
+  @Parameterized.Parameters(name = "flag: {0}")
+  public static Boolean[] data() {
+    return new Boolean[] {true, false};
+  }
+
+  public InterfaceTest(boolean flag) {
+    this.flag = flag;
+  }
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    user = new InterfaceUser(fld);
+  }
+
+  @Test
+  public void test() {
+    if (flag) {
+      user.consume();
+    }
+    verify(fld, times(flag ? 1 : 0)).onEnterForeground();
+  }
+}
diff --git a/src/test/examples/mockito_interface/InterfaceUser.java b/src/test/examples/mockito_interface/InterfaceUser.java
new file mode 100644
index 0000000..67729d6
--- /dev/null
+++ b/src/test/examples/mockito_interface/InterfaceUser.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, 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 mockito_interface;
+
+public class InterfaceUser {
+
+  private Interface itf;
+
+  public InterfaceUser(Interface itf) {
+    this.itf = itf;
+  }
+
+  void consume() {
+    itf.onEnterForeground();
+  }
+
+  public static void main() {
+    Implementer impl = new Implementer();
+    InterfaceUser user = new InterfaceUser(impl);
+    user.consume();
+  }
+
+}
diff --git a/src/test/examples/mockito_interface/keep-rules-conditional-on-mock.txt b/src/test/examples/mockito_interface/keep-rules-conditional-on-mock.txt
new file mode 100644
index 0000000..67f437f
--- /dev/null
+++ b/src/test/examples/mockito_interface/keep-rules-conditional-on-mock.txt
@@ -0,0 +1,23 @@
+# Copyright (c) 2018, 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.
+
+-keepattributes *Annotation*
+
+-keep class **.InterfaceUser {
+  public static void main(...);
+}
+
+-keep class org.junit.** { *; }
+-keep class org.mockito.** { *; }
+-keep @**.RunWith class * { *; }
+
+# Mockito generates mocks of interface types at runtime. If interface methods are optimized, i.e.,
+# stripped out, mock-based tests will fail. So, keep all methods of interfaces if they are used as
+# field type and annotated with @Mock.
+-if class * {
+  @org.mockito.Mock * *;
+}
+-keep interface <2> {
+  <methods>;
+}
\ No newline at end of file
diff --git a/src/test/examples/mockito_interface/keep-rules.txt b/src/test/examples/mockito_interface/keep-rules.txt
new file mode 100644
index 0000000..40dd170
--- /dev/null
+++ b/src/test/examples/mockito_interface/keep-rules.txt
@@ -0,0 +1,13 @@
+# Copyright (c) 2018, 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.
+
+-keepattributes *Annotation*
+
+-keep class **.InterfaceUser {
+  public static void main(...);
+}
+
+-keep class org.junit.** { *; }
+-keep class org.mockito.** { *; }
+-keep @**.RunWith class * { *; }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 46ae905..1691232 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
 
@@ -25,6 +28,10 @@
 
   public abstract T noMinification();
 
+  public T addKeepRules(Path path) throws IOException {
+    return addKeepRules(FileUtils.readAllLines(path));
+  }
+
   public abstract T addKeepRules(Collection<String> rules);
 
   public T addKeepRules(String... rules) {
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
new file mode 100644
index 0000000..f3db17e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, 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.shaking.proxy;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MockitoTest extends TestBase {
+  private static final String M_I_PKG = "mockito_interface";
+  private static final String M_I = M_I_PKG + ".Interface";
+  private static final Path MOCKITO_INTERFACE_JAR =
+      Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, M_I_PKG + FileUtils.JAR_EXTENSION);
+
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public MockitoTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void b120675359_devirtualized() throws Exception {
+    Path flagToKeepTestRunner = Paths.get(ToolHelper.EXAMPLES_DIR, M_I_PKG, "keep-rules.txt");
+    CodeInspector inspector = testForR8(backend)
+        .addProgramFiles(MOCKITO_INTERFACE_JAR)
+        .addKeepRules(flagToKeepTestRunner)
+        .noMinification()
+        .compile()
+        .inspector();
+    ClassSubject itf = inspector.clazz(M_I);
+    assertThat(itf, isPresent());
+    MethodSubject mtd = itf.method("void", "onEnterForeground");
+    assertThat(mtd, not(isPresent()));
+  }
+
+  @Test
+  public void b120675359_conditional_keep() throws Exception {
+    Path flagToKeepInterfaceConditionally =
+        Paths.get(ToolHelper.EXAMPLES_DIR, M_I_PKG, "keep-rules-conditional-on-mock.txt");
+    CodeInspector inspector = testForR8(backend)
+        .addProgramFiles(MOCKITO_INTERFACE_JAR)
+        .addKeepRules(flagToKeepInterfaceConditionally)
+        .noMinification()
+        .compile()
+        .inspector();
+    ClassSubject itf = inspector.clazz(M_I);
+    assertThat(itf, isPresent());
+    MethodSubject mtd = itf.method("void", "onEnterForeground");
+    assertThat(mtd, isPresent());
+  }
+
+}