blob: 5d95ca05b226853badca57111ca90da0d0c91527 [file] [log] [blame]
// 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 static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class AssumenosideeffectsPropagationTest extends TestBase {
private static final Class<?> MAIN = SubsUser.class;
enum TestConfig {
SPECIFIC_RULES,
NON_SPECIFIC_RULES_PARTIAL,
NON_SPECIFIC_RULES_ALL;
public String getKeepRules() {
switch (this) {
case SPECIFIC_RULES:
return StringUtils.lines(
// Intentionally miss sub types of Base2 for info().
"-assumenosideeffects class **.*$Sub* {",
" void info(...); ",
"}",
// All debug() and verbose() should be removed.
"-assumenosideeffects class **.*Sub* {",
" void debug(...);",
" void verbose(...);",
"}"
);
case NON_SPECIFIC_RULES_PARTIAL:
return StringUtils.lines(
// Intentionally miss sub types of Base2 for debug().
"-assumenosideeffects class **.*$Sub* {",
" void debug(...);"
,"}",
// Targeting info() and verbose() in Base1, without wildcards.
"-assumenosideeffects class * extends " + Base1.class.getTypeName() + " {",
" void info(...);",
" void verbose(...);",
"}",
// Targeting info() and verbose() in Base2, with wildcards.
"-assumenosideeffects class * extends " + Base2.class.getTypeName() + " {",
" void *o*(java.lang.String);",
"}"
);
case NON_SPECIFIC_RULES_ALL:
return StringUtils.lines(
"-assumenosideeffects class **.*Sub* {",
" void *(java.lang.String);",
"}"
);
default:
throw new Unreachable();
}
}
public String expectedOutput(boolean enableHorizontalClassMerging) {
switch (this) {
case SPECIFIC_RULES:
return enableHorizontalClassMerging
? StringUtils.lines(
"[Base2, info]: message08",
// Base2#info also has side effects.
"[Base2, info]: message4",
"The end")
: StringUtils.lines(
// Itf#info has side effects due to the missing Base2.
"[Sub1, info]: message00",
"[Base2, info]: message08",
// Base2#info also has side effects.
"[Base2, info]: message4",
"The end");
case NON_SPECIFIC_RULES_PARTIAL:
return enableHorizontalClassMerging
? StringUtils.lines(
"[AnotherSub2, debug]: message08", "[AnotherSub2, debug]: message5", "The end")
: StringUtils.lines(
"[Base1, debug]: message00",
"[AnotherSub2, debug]: message08",
"[AnotherSub2, debug]: message5",
"The end");
case NON_SPECIFIC_RULES_ALL:
return StringUtils.lines("The end");
default:
throw new Unreachable();
}
}
}
private final TestParameters parameters;
private final TestConfig config;
private final boolean enableHorizontalClassMerging;
@Parameterized.Parameters(name = "{0}, config: {1}, horizontal: {2}")
public static Collection<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimesAndApiLevels().build(),
TestConfig.values(),
BooleanUtils.values());
}
public AssumenosideeffectsPropagationTest(
TestParameters parameters, TestConfig config, boolean enableHorizontalClassMerging) {
this.parameters = parameters;
this.config = config;
this.enableHorizontalClassMerging = enableHorizontalClassMerging;
}
@Test
public void testJVMOutput() throws Exception {
assumeTrue(parameters.isCfRuntime());
assumeTrue(config == TestConfig.SPECIFIC_RULES);
assumeFalse(enableHorizontalClassMerging);
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines(
"[Sub1, info]: message00",
"[Base1, debug]: message00",
"[Sub1, verbose]: message00",
"[Sub1, info]: message1",
"[Base1, debug]: message2",
"[Sub1, verbose]: message3",
"[Base2, info]: message08",
"[AnotherSub2, debug]: message08",
"[AnotherSub2, verbose]: message08",
"[Base2, info]: message4",
"[AnotherSub2, debug]: message5",
"[AnotherSub2, verbose]: message6",
"The end");
}
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(AssumenosideeffectsPropagationTest.class)
.addKeepMainRule(MAIN)
.addKeepRules(config.getKeepRules())
.addOptionsModification(
options ->
options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
.enableInliningAnnotations()
.addDontObfuscate()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(config.expectedOutput(enableHorizontalClassMerging));
}
interface Itf {
void info(String message);
void debug(String message);
void verbose(String message);
}
static abstract class Base1 implements Itf {
public abstract void info(String message);
public abstract void debug(String message);
public abstract void verbose(String message);
}
static class Sub1 extends Base1 {
@Override
public void info(String message) {
System.out.println("[Sub1, info]: " + message);
}
@Override
public void debug(String message) {
System.out.println("[Base1, debug]: " + message);
}
@Override
public void verbose(String message) {
System.out.println("[Sub1, verbose]: " + message);
}
}
static class Sub2 extends Base1 {
@Override
public void info(String message) {
System.out.println("[Sub2, info]: " + message);
}
@Override
public void debug(String message) {
System.out.println("[Base1, debug]: " + message);
}
@Override
public void verbose(String message) {
System.out.println("[Sub2, verbose]: " + message);
}
}
static abstract class Base2 implements Itf {
public abstract void info(String message);
public abstract void debug(String message);
public abstract void verbose(String message);
}
static class AnotherSub1 extends Base2 {
@Override
public void info(String message) {
System.out.println("[Base2, info]: " + message);
}
@Override
public void debug(String message) {
System.out.println("[AnotherSub1, debug]: " + message);
}
@Override
public void verbose(String message) {
System.out.println("[AnotherSub1, verbose]: " + message);
}
}
static class AnotherSub2 extends Base2 {
@Override
public void info(String message) {
System.out.println("[Base2, info]: " + message);
}
@Override
public void debug(String message) {
System.out.println("[AnotherSub2, debug]: " + message);
}
@Override
public void verbose(String message) {
System.out.println("[AnotherSub2, verbose]: " + message);
}
}
static class SubsUser {
static Base1 createBase1() {
return System.currentTimeMillis() > 0 ? new Sub1() : new Sub2();
}
static Base2 createBase2() {
return System.currentTimeMillis() < 0 ? new AnotherSub1() : new AnotherSub2();
}
@NeverInline
private static void testInvokeInterface(Itf itf, String message) {
itf.info(message);
itf.debug(message);
itf.verbose(message);
}
public static void main(String... args) {
Base1 instance1 = createBase1();
testInvokeInterface(instance1, "message00");
instance1.info("message1");
instance1.debug("message2");
instance1.verbose("message3");
Base2 instance2 = createBase2();
testInvokeInterface(instance2, "message08");
instance2.info("message4");
instance2.debug("message5");
instance2.verbose("message6");
System.out.println("The end");
}
}
}