Add tests about over propagation of assume* rules.

Bug: 137938214
Change-Id: I934f96f37ca75c019a38ab5459c6b80f1537a7a4
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
new file mode 100644
index 0000000..a9dda0d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
@@ -0,0 +1,245 @@
+// 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.shaking.assumenosideeffects;
+
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AssumenosideeffectsVisibleMethodsTest extends TestBase {
+  private static final Class<?> MAIN = TestClass.class;
+
+  enum TestConfig {
+    RULE_THAT_REFERS_LIB_BASE,
+    RULE_THAT_REFERS_PRG_BASE,
+    RULE_THAT_REFERS_PRG_SUB,
+    RULE_WITH_EXTENDS_LIB_BASE,
+    RULE_WITH_EXTENDS_PRG_BASE;
+    // The remaining case(s) are not tested since no items are matched.
+    // RULE_WITH_EXTENDS_PRG_SUB;
+
+    public String getKeepRule() {
+      switch (this) {
+        case RULE_THAT_REFERS_LIB_BASE:
+          return StringUtils.lines(
+              "-assumenosideeffects class " + LibraryBase.class.getTypeName() + " {",
+              "  *;",
+              "}");
+        case RULE_THAT_REFERS_PRG_BASE:
+          return StringUtils.lines(
+              "-assumenosideeffects class " + ProgramBase.class.getTypeName() + " {",
+              "  *;",
+              "}");
+        case RULE_THAT_REFERS_PRG_SUB:
+          return StringUtils.lines(
+              "-assumenosideeffects class " + ProgramSub.class.getTypeName() + " {",
+              "  *;",
+              "}");
+        case RULE_WITH_EXTENDS_LIB_BASE:
+          return StringUtils.lines(
+              "-assumenosideeffects class * extends " + LibraryBase.class.getTypeName() + " {",
+              "  *;",
+              "}");
+        case RULE_WITH_EXTENDS_PRG_BASE:
+          return StringUtils.lines(
+              "-assumenosideeffects class * extends " + ProgramBase.class.getTypeName() + " {",
+              "  *;",
+              "}");
+      }
+      throw new Unreachable();
+    }
+
+    public String expectedOutput() {
+      switch (this) {
+        case RULE_THAT_REFERS_LIB_BASE:
+          return StringUtils.lines(
+              "Unless LibraryBase is explicitly mentioned",
+              // TODO(b/137938214): Should not mark Program*'s methods.
+              // "Nah; no more throwing."
+              // "[ProgramBase]: as long as ProgramBase is specified"
+              // "No, no, no; no more throwing."
+              // "[ProgramSub]: as long as ProgramSub is specified"
+              "The end");
+        case RULE_THAT_REFERS_PRG_BASE:
+          return StringUtils.lines(
+              // TODO(b/137938214): Should not mark LibraryBase's methods.
+              // "Throwing!",
+              "Unless LibraryBase is explicitly mentioned",
+              // "[LibraryBase]: as long as LibraryBase is specified"
+              // TODO(b/137938214): Should we mark ProgramSub's methods?
+              //   Those are clearly an instance of ProgramSub type, and invocations with it.
+              //   One caveat: ProgramSub#throwing doesn't exist, so ProgramBase#throwing will be
+              //   used after the resolution, which is specified as side effect free.
+              // "[ProgramSub]: as long as ProgramSub is specified"
+              "The end");
+        case RULE_THAT_REFERS_PRG_SUB:
+          return StringUtils.lines(
+              // TODO(b/137938214): Should not mark LibraryBase's methods.
+              // "Throwing!",
+              "Unless LibraryBase is explicitly mentioned",
+              // TODO(b/137938214): Should not mark *Base's #debug(...)
+              // "[LibraryBase]: as long as LibraryBase is specified"
+              // "[ProgramBase]: as long as ProgramBase is specified"
+              "The end");
+        case RULE_WITH_EXTENDS_LIB_BASE:
+          return StringUtils.lines(
+              // TODO(b/137938214): Should not mark LibraryBase's methods.
+              // "Throwing!",
+              "Unless LibraryBase is explicitly mentioned",
+              // "[LibraryBase]: as long as LibraryBase is specified"
+              "The end");
+        case RULE_WITH_EXTENDS_PRG_BASE:
+          return StringUtils.lines(
+              // TODO(b/137938214): Should not mark LibraryBase's methods.
+              // "Throwing!",
+              "Unless LibraryBase is explicitly mentioned",
+              // "[LibraryBase]: as long as LibraryBase is specified"
+              "The end");
+      }
+      throw new Unreachable();
+    }
+  }
+
+  @Parameterized.Parameters(name = "{0} {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(getTestParameters().withAllRuntimes().build(), TestConfig.values());
+  }
+
+  private final TestParameters parameters;
+  private final TestConfig config;
+
+  public AssumenosideeffectsVisibleMethodsTest(TestParameters parameters, TestConfig config) {
+    this.parameters = parameters;
+    this.config = config;
+  }
+
+  @ClassRule
+  public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
+
+  private static Path libJarPath;
+  private static Path libDexPath;
+
+  @BeforeClass
+  public static void prepareLib() throws Exception {
+    libJarPath = staticTemp.newFile("lib.jar").toPath().toAbsolutePath();
+    writeClassesToJar(libJarPath, ImmutableList.of(LibraryBase.class));
+    libDexPath = staticTemp.newFile("lib.dex").toPath().toAbsolutePath();
+    testForD8(staticTemp)
+        .addProgramFiles(libJarPath)
+        .setMinApi(AndroidApiLevel.B)
+        .setProgramConsumer(new ArchiveConsumer(libDexPath))
+        .compile();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(libJarPath)
+        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+        .addProgramClasses(ProgramBase.class, ProgramSub.class, MAIN)
+        .addKeepMainRule(MAIN)
+        .addKeepRules(config.getKeepRule())
+        .noMinification()
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .addRunClasspathFiles(parameters.isDexRuntime() ? libDexPath : libJarPath)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(config.expectedOutput());
+  }
+
+  static class LibraryBase {
+    protected void throwing(String message) {
+      throw new RuntimeException(message);
+    }
+
+    protected void debug(String message) {
+      System.out.println("[LibraryBase]: " + message);
+    }
+  }
+
+  @NeverClassInline
+  @NeverMerge
+  static class ProgramBase extends LibraryBase {
+    @NeverInline
+    @Override
+    protected void throwing(String message) {
+      System.out.println(message + "; no more throwing.");
+    }
+
+    @NeverInline
+    @Override
+    protected void debug(String message) {
+      System.out.println("[ProgramBase]: " + message);
+    }
+  }
+
+  @NeverClassInline
+  static class ProgramSub extends ProgramBase {
+    @NeverInline
+    @Override
+    protected void debug(String message) {
+      System.out.println("[ProgramSub]: " + message);
+    }
+  }
+
+  static class TestClass {
+    @NeverInline
+    static LibraryBase createLibraryInstance() {
+      return System.currentTimeMillis() > 0 ? new LibraryBase() : new ProgramBase();
+    }
+
+    @NeverInline
+    static ProgramBase createProgramInstance() {
+      return System.currentTimeMillis() > 0 ? new ProgramBase() : new ProgramSub();
+    }
+
+    public static void main(String... args) {
+      LibraryBase libInstance = createLibraryInstance();
+      try {
+        libInstance.throwing("Throwing!");
+        System.out.println("Unless LibraryBase is explicitly mentioned");
+      } catch (RuntimeException e) {
+        System.out.println("Expect to catch RuntimeException");
+      }
+      libInstance.debug("as long as LibraryBase is specified");
+
+      ProgramBase prgInstance = createProgramInstance();
+      try {
+        prgInstance.throwing("Nah");
+      } catch (RuntimeException e) {
+        System.out.println("Won't be any RuntimeException");
+      }
+      prgInstance.debug("as long as ProgramBase is specified");
+
+      ProgramSub subInstance = new ProgramSub();
+      try {
+        subInstance.throwing("No, no, no");
+      } catch (RuntimeException e) {
+        System.out.println("Won't be any RuntimeException");
+      }
+      subInstance.debug("as long as ProgramSub is specified");
+      System.out.println("The end");
+    }
+  }
+}