blob: 89d12ebe1f0fb664e433cf26e5da22d66b00a79e [file]
// Copyright (c) 2025, 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.memberrebinding;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestBuilder;
import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.memberrebinding.testclasses.MemberRebindingClasspathSplitTestClasses;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class MemberRebindingClasspathSplitTest extends TestBase {
@Parameter(0)
public TestParameters parameters;
private static class TestConfig {
private final String desc;
private final ThrowableConsumer<? super TestBuilder<?, ?>> addToClasspath;
private final ThrowableConsumer<? super TestBuilder<?, ?>> addToProgrampath;
private final ThrowableConsumer<? super TestCompileResult<?, ?>> addToRunClasspath;
private final boolean expectFailure;
private TestConfig(
String desc,
ThrowableConsumer<? super TestBuilder<?, ?>> addToClasspath,
ThrowableConsumer<? super TestBuilder<?, ?>> addToProgrampath,
ThrowableConsumer<? super TestCompileResult<?, ?>> addToRunClasspath,
boolean expectFailure) {
this.desc = desc;
this.addToClasspath = addToClasspath;
this.addToProgrampath = addToProgrampath;
this.addToRunClasspath = addToRunClasspath;
this.expectFailure = expectFailure;
}
@Override
public String toString() {
return desc;
}
}
@Parameter(1)
public TestConfig split;
@Parameters(name = "{0}, classpathsplit: {1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimesAndApiLevels().build(),
ImmutableList.of(
new TestConfig(
"Both A and B on classpath",
b -> {
b.addClasspathClasses(
MemberRebindingClasspathSplitTestClasses.getA(),
MemberRebindingClasspathSplitTestClasses.getB());
},
b -> {},
b -> {
b.addRunClasspathClasses(
MemberRebindingClasspathSplitTestClasses.getA(),
MemberRebindingClasspathSplitTestClasses.getB());
},
false),
new TestConfig(
"Both A and B on classpath, m() bridge removed from B",
b -> {
b.addClasspathClasses(MemberRebindingClasspathSplitTestClasses.getA());
b.addClasspathClassFileData(
transformer(MemberRebindingClasspathSplitTestClasses.getB())
.removeMethodsWithName("m")
.transform());
},
b -> {},
b -> {
b.addRunClasspathClasses(MemberRebindingClasspathSplitTestClasses.getA());
b.addRunClasspathClassFileData(
transformer(MemberRebindingClasspathSplitTestClasses.getB())
.removeMethodsWithName("m")
.transform());
},
false),
new TestConfig(
"A on classpath and B on programpath",
b -> {
b.addClasspathClasses(MemberRebindingClasspathSplitTestClasses.getA());
},
b -> {
b.addProgramClasses(MemberRebindingClasspathSplitTestClasses.getB());
},
b -> {
b.addRunClasspathClasses(MemberRebindingClasspathSplitTestClasses.getA());
},
false),
new TestConfig(
"A on classpath and B on programpath, m() bridge removed from B",
b -> {
b.addClasspathClasses(MemberRebindingClasspathSplitTestClasses.getA());
},
b -> {
b.addProgramClassFileData(
transformer(MemberRebindingClasspathSplitTestClasses.getB())
.removeMethodsWithName("m")
.transform());
},
b -> {
b.addRunClasspathClasses(MemberRebindingClasspathSplitTestClasses.getA());
},
false),
new TestConfig(
"Both A and B on classpath but neither A or B is public",
b -> {
b.addClasspathClasses(MemberRebindingClasspathSplitTestClasses.getA());
b.addClasspathClassFileData(
transformer(MemberRebindingClasspathSplitTestClasses.getB())
.setAccessFlags(AccessFlags::unsetPublic)
.transform());
},
b -> {},
b -> {
b.addRunClasspathClasses(MemberRebindingClasspathSplitTestClasses.getA());
b.addRunClasspathClassFileData(
transformer(MemberRebindingClasspathSplitTestClasses.getB())
.setAccessFlags(AccessFlags::unsetPublic)
.transform());
},
true)));
}
@Test
public void testR8() throws Exception {
testForR8(parameters)
.addInnerClasses(getClass())
.apply(split.addToClasspath)
.apply(split.addToProgrampath)
.allowStdoutMessages()
.addDontObfuscate()
.addDontOptimize()
.addKeepRules("-keep class " + Main.class.getTypeName() + " { void main(...); }")
.compile()
.apply(split.addToRunClasspath)
.run(parameters.getRuntime(), Main.class)
.applyIf(
split.expectFailure && parameters.isDexRuntimeVersionOlderThanOrEqual(Version.V4_4_4),
rr -> rr.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
split.expectFailure,
rr -> rr.assertFailureWithErrorThatThrows(IllegalAccessError.class),
rr -> rr.assertSuccessWithOutputLines("A", "A"));
}
public static class C extends MemberRebindingClasspathSplitTestClasses.B {
public String superm() {
return super.m();
}
}
public static class Main {
public static void main(String[] strArr) {
C c = new C();
System.out.println(c.m());
System.out.println(c.superm());
}
}
}