blob: 85ab5564f662a2ae2bd732eb8784e4ca748cf3ab [file] [log] [blame]
// Copyright (c) 2023, 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.apimodel;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class ApiModelClassMergingPackagePrivateTest extends TestBase {
private final AndroidApiLevel mockLevel = AndroidApiLevel.T;
private final String newPackageBinaryName = "package/a/";
private final String newADescriptor = "L" + newPackageBinaryName + "A;";
private final String newCallerDescriptor = "L" + newPackageBinaryName + "Caller;";
private final TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
}
public ApiModelClassMergingPackagePrivateTest(TestParameters parameters) {
this.parameters = parameters;
}
private boolean isGreaterOrEqualToMockLevel() {
return parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
}
private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
testBuilder
.addProgramClasses(B.class)
.addProgramClassFileData(
transformer(A.class).setClassDescriptor(newADescriptor).transform(),
transformer(Caller.class)
.setClassDescriptor(newCallerDescriptor)
.replaceClassDescriptorInMembers(descriptor(A.class), newADescriptor)
.replaceClassDescriptorInMethodInstructions(descriptor(A.class), newADescriptor)
.transform(),
transformer(Main.class)
.replaceClassDescriptorInMembers(descriptor(A.class), newADescriptor)
.replaceClassDescriptorInMethodInstructions(descriptor(A.class), newADescriptor)
.replaceClassDescriptorInMembers(descriptor(Caller.class), newCallerDescriptor)
.replaceClassDescriptorInMethodInstructions(
descriptor(Caller.class), newCallerDescriptor)
.transform())
.addLibraryClasses(Api1.class, Api2.class)
.addDefaultRuntimeLibrary(parameters)
.setMinApi(parameters.getApiLevel())
.apply(ApiModelingTestHelper::enableOutliningOfMethods)
.apply(b -> setApiLevels(b, Api1.class))
.apply(b -> setApiLevels(b, Api2.class));
}
private void setApiLevels(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder, Class<?> apiClass) {
testBuilder
.apply(setMockApiLevelForClass(apiClass, mockLevel))
.apply(setMockApiLevelForDefaultInstanceInitializer(apiClass, mockLevel))
.apply(
setMockApiLevelForMethod(
Reference.method(
Reference.classFromClass(apiClass), "foo", Collections.emptyList(), null),
mockLevel));
}
@Test
public void testD8Debug() throws Exception {
testForD8(parameters.getBackend())
.setMode(CompilationMode.DEBUG)
.apply(this::setupTestBuilder)
.addHorizontallyMergedClassesInspector(
HorizontallyMergedClassesInspector::assertNoClassesMerged)
.compile()
.addBootClasspathClasses(Api1.class, Api2.class)
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
}
@Test
public void testD8Release() throws Exception {
testForD8(parameters.getBackend())
.setMode(CompilationMode.RELEASE)
.apply(this::setupTestBuilder)
.addHorizontallyMergedClassesInspector(
HorizontallyMergedClassesInspector::assertNoClassesMerged)
.compile()
.addBootClasspathClasses(Api1.class, Api2.class)
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
}
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.apply(this::setupTestBuilder)
.addKeepMainRule(Main.class)
.addDontObfuscate()
.addHorizontallyMergedClassesInspectorIf(
parameters.isCfRuntime(), HorizontallyMergedClassesInspector::assertNoClassesMerged)
.addHorizontallyMergedClassesInspectorIf(
!parameters.isCfRuntime(), this::inspectHorizontallyMergedClasses)
.compile()
.addBootClasspathClasses(Api1.class, Api2.class)
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
}
private void inspectHorizontallyMergedClasses(HorizontallyMergedClassesInspector inspector) {
if (isGreaterOrEqualToMockLevel()) {
inspector.assertNoClassesMerged();
} else {
inspector.assertClassReferencesMerged(
SyntheticItemsTestUtils.syntheticApiOutlineClass(Reference.classFromClass(Main.class), 0),
SyntheticItemsTestUtils.syntheticApiOutlineClass(
Reference.classFromDescriptor(newCallerDescriptor), 0));
}
}
private void checkOutput(SingleTestRunResult<?> runResult) {
runResult.assertSuccessWithOutputLines("Api1::foo", "Api2::foo");
}
public static class Api1 {
public void foo() {
System.out.println("Api1::foo");
}
}
public static class Api2 {
public void foo() {
System.out.println("Api2::foo");
}
}
static class /* package.A. */ A extends Api1 {}
public static class /* package.A. */ Caller {
public static void createAndCallFoo() {
new A().foo();
}
}
static class B extends Api2 {}
public static class Main {
public static void main(String[] args) throws Exception {
Caller.createAndCallFoo();
new B().foo();
}
}
}