Add tests for interface initialization

Bug: 172050082
Bug: 172049649
Bug: 144266257
Change-Id: I82099d8606ceb7c1b06e5dbdc87f4156568e4c7c
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f18a09f..1b417b3 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -8,7 +8,6 @@
 import static com.google.common.collect.Lists.cartesianProduct;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.DataResourceProvider.Visitor;
@@ -77,7 +76,6 @@
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
-import com.android.tools.r8.utils.ThrowingAction;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -584,6 +582,12 @@
     return builder;
   }
 
+  protected static AndroidApp.Builder buildInnerClasses(Class<?> clazz) throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addProgramFiles(ToolHelper.getClassFilesForInnerClasses(clazz));
+    return builder;
+  }
+
   protected static AndroidApp readClassesAndRuntimeJar(
       List<Class<?>> programClasses, Backend backend) throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassMayHaveInitializationSideEffectsTestBase.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassMayHaveInitializationSideEffectsTestBase.java
new file mode 100644
index 0000000..fc1a01e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassMayHaveInitializationSideEffectsTestBase.java
@@ -0,0 +1,36 @@
+// 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.shaking.clinit;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ClassMayHaveInitializationSideEffectsTestBase extends TestBase {
+
+  public void assertMayHaveClassInitializationSideEffects(
+      AppView<AppInfoWithLiveness> appView, Class<?> clazz) {
+    assertMayHaveClassInitializationSideEffectsEquals(appView, clazz, true);
+  }
+
+  public void assertNoClassInitializationSideEffects(
+      AppView<AppInfoWithLiveness> appView, Class<?> clazz) {
+    assertMayHaveClassInitializationSideEffectsEquals(appView, clazz, false);
+  }
+
+  public void assertMayHaveClassInitializationSideEffectsEquals(
+      AppView<AppInfoWithLiveness> appView, Class<?> clazz, boolean expected) {
+    DexType type = toDexType(clazz, appView.dexItemFactory());
+    DexProgramClass programClass = asProgramClassOrNull(appView.appInfo().definitionFor(type));
+    assertNotNull(programClass);
+    assertEquals(expected, programClass.classInitializationMayHaveSideEffects(appView));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java
new file mode 100644
index 0000000..2e0f754
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -0,0 +1,109 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByInvokeStaticOnSubInterfaceTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InterfaceInitializedByInvokeStaticOnSubInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForDesugaring(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            DesugarTestConfiguration::isJavac,
+            runResult -> runResult.assertSuccessWithOutputLines("J"))
+        .applyIf(
+            DesugarTestConfiguration::isNotJavac,
+            runResult -> {
+              if (parameters
+                  .getApiLevel()
+                  .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
+                runResult.assertSuccessWithOutputLines("J");
+              } else {
+                // TODO(b/172050082): Calling greet() on the companion class of J should trigger J's
+                //  class initializer.
+                runResult.assertSuccessWithEmptyOutput();
+              }
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("J");
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("J");
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    assertMayHaveClassInitializationSideEffects(appView, J.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      J.greet();
+    }
+  }
+
+  interface I {
+
+    Greeter iGreeter = new Greeter("I");
+  }
+
+  interface J extends I {
+
+    Greeter jGreeter = new Greeter("J");
+
+    static void greet() {}
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java
new file mode 100644
index 0000000..4f2eb00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java
@@ -0,0 +1,104 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByInvokeStaticTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InterfaceInitializedByInvokeStaticTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForDesugaring(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            DesugarTestConfiguration::isJavac,
+            runResult -> runResult.assertSuccessWithOutputLines("I"))
+        .applyIf(
+            DesugarTestConfiguration::isNotJavac,
+            runResult -> {
+              if (parameters
+                  .getApiLevel()
+                  .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
+                runResult.assertSuccessWithOutputLines("I");
+              } else {
+                // TODO(b/172050082): Calling greet() on the companion class of I should trigger I's
+                //  class initializer.
+                runResult.assertSuccessWithEmptyOutput();
+              }
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("I");
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("I");
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    assertMayHaveClassInitializationSideEffects(appView, I.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I.greet();
+    }
+  }
+
+  interface I {
+
+    Greeter greeter = new Greeter("I");
+
+    static void greet() {}
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java
new file mode 100644
index 0000000..d20f13a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java
@@ -0,0 +1,94 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByStaticGetOnSubClassTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceInitializedByStaticGetOnSubClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    assertMayHaveClassInitializationSideEffects(appView, A.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Greeter greeter = A.aGreeter;
+    }
+  }
+
+  interface I {
+
+    Greeter iGreeter = new Greeter("I");
+  }
+
+  static class A implements I {
+
+    static Greeter aGreeter = new Greeter("A");
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java
new file mode 100644
index 0000000..bb59d8c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java
@@ -0,0 +1,94 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByStaticGetOnSubInterfaceTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceInitializedByStaticGetOnSubInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("J");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("J");
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("J");
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    assertMayHaveClassInitializationSideEffects(appView, J.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Greeter greeter = J.jGreeter;
+    }
+  }
+
+  interface I {
+
+    Greeter iGreeter = new Greeter("I");
+  }
+
+  interface J extends I {
+
+    Greeter jGreeter = new Greeter("J");
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java
new file mode 100644
index 0000000..a3e93ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java
@@ -0,0 +1,89 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByStaticGetTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceInitializedByStaticGetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("I");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("I");
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("I");
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    assertMayHaveClassInitializationSideEffects(appView, I.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Greeter greeter = I.greeter;
+    }
+  }
+
+  interface I {
+
+    Greeter greeter = new Greeter("I");
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java
new file mode 100644
index 0000000..88c1089
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java
@@ -0,0 +1,95 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceNotInitializedByInvokeStaticOnSubClassTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceNotInitializedByInvokeStaticOnSubClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    // TODO(b/172049649): Initialization of A does not have side effects.
+    assertMayHaveClassInitializationSideEffects(appView, A.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A.greet();
+    }
+  }
+
+  interface I {
+
+    Greeter iGreeter = new Greeter("I");
+  }
+
+  static class A implements I {
+
+    static void greet() {}
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java
new file mode 100644
index 0000000..c69a6d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java
@@ -0,0 +1,112 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(
+            runResult -> {
+              if (parameters.isCfRuntime()
+                  || parameters
+                      .getApiLevel()
+                      .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
+                runResult.assertSuccessWithOutputLines("I");
+              } else {
+                // On older Android runtimes there is no default interface methods and therefore the
+                // semantics is different.
+                runResult.assertSuccessWithEmptyOutput();
+              }
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        // TODO(b/144266257): This should succeed with "I" when default interface methods are
+        //  supported, but we remove the default method I.m() because it is unused, which changes
+        //  the behavior.
+        .assertSuccessWithEmptyOutput();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("I");
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    assertMayHaveClassInitializationSideEffects(appView, A.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A.greet();
+    }
+  }
+
+  interface I {
+
+    Greeter iGreeter = new Greeter("I");
+
+    default void m() {}
+  }
+
+  static class A implements I {
+
+    static void greet() {}
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java
new file mode 100644
index 0000000..a31b2ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java
@@ -0,0 +1,111 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(
+            runResult -> {
+              if (parameters.isCfRuntime()
+                  || parameters
+                      .getApiLevel()
+                      .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
+                runResult.assertSuccessWithOutputLines("I", "A");
+              } else {
+                // On older Android runtimes there is no default interface methods and therefore the
+                // semantics is different.
+                runResult.assertSuccessWithOutputLines("A");
+              }
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        // TODO(b/144266257): This should succeed with "I\nA" when default interface methods are
+        //  supported, but we remove the default method I.m() because it is unused, which changes
+        //  the behavior.
+        .assertSuccessWithOutputLines("A");
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("I", "A");
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    assertMayHaveClassInitializationSideEffects(appView, A.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Greeter greeter = A.aGreeter;
+    }
+  }
+
+  interface I {
+
+    Greeter iGreeter = new Greeter("I");
+
+    default void m() {}
+  }
+
+  static class A implements I {
+
+    static Greeter aGreeter = new Greeter("A");
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java
new file mode 100644
index 0000000..1977be8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java
@@ -0,0 +1,97 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("J");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("J");
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("J");
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    assertMayHaveClassInitializationSideEffects(appView, J.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Greeter greeter = J.jGreeter;
+    }
+  }
+
+  interface I {
+
+    Greeter iGreeter = new Greeter("I");
+
+    default void m() {}
+  }
+
+  interface J extends I {
+
+    Greeter jGreeter = new Greeter("J");
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java
new file mode 100644
index 0000000..115d6e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -0,0 +1,98 @@
+// 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest
+    extends ClassMayHaveInitializationSideEffectsTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .allowStdoutMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  @Test
+  public void testClassInitializationMayHaveSideEffects() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+    // TODO(b/172049649): The initialization of J does not trigger the <clinit> of I.
+    assertMayHaveClassInitializationSideEffects(appView, J.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      J.greet();
+    }
+  }
+
+  interface I {
+
+    Greeter iGreeter = new Greeter("I");
+
+    default void m() {}
+  }
+
+  interface J extends I {
+
+    static void greet() {}
+  }
+
+  static class Greeter {
+
+    Greeter(String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}