blob: 774de69f801a09e5ccaf4c17d367bc39b5e5fe30 [file] [log] [blame]
// Copyright (c) 2022, 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.setMockApiLevelForMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.OutputMode;
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.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
import java.util.ArrayList;
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 ApiModelHorizontalMergeAndD8MergeTest extends TestBase {
private final AndroidApiLevel mockLevel = AndroidApiLevel.M;
@Parameter() public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withDexRuntimes().withAllApiLevels().build();
}
private boolean isGreaterOrEqualToMockLevel() {
return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
}
private void setupTestCompileBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
throws NoSuchMethodException {
testBuilder
.addLibraryClasses(LibraryClass.class)
.addDefaultRuntimeLibrary(parameters)
.setMinApi(parameters.getApiLevel())
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
.apply(ApiModelingTestHelper::enableOutliningOfMethods)
.apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
.apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), mockLevel))
.apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("bar"), mockLevel));
}
private boolean addToBootClasspath() {
return parameters.isDexRuntime()
&& parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockLevel);
}
@Test
public void testD8DebugDexFilePerClassFile() throws Exception {
testD8Merge(CompilationMode.DEBUG);
}
@Test
public void testD8ReleaseDexFilePerClassFile() throws Exception {
testD8Merge(CompilationMode.RELEASE);
}
private Path runD8ForClass(
Class<?> clazz, GlobalSyntheticsTestingConsumer global, CompilationMode mode)
throws Exception {
return testForD8()
.addProgramClasses(clazz)
.setMode(mode)
.setOutputMode(OutputMode.DexFilePerClassFile)
.apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(global))
.apply(this::setupTestCompileBuilder)
.compile()
.writeToZip();
}
public void testD8Merge(CompilationMode mode) throws Exception {
List<Path> paths = new ArrayList<>();
GlobalSyntheticsTestingConsumer mainGlobals = new GlobalSyntheticsTestingConsumer();
GlobalSyntheticsTestingConsumer testCallingFooGlobals = new GlobalSyntheticsTestingConsumer();
GlobalSyntheticsTestingConsumer testCallingBarGlobals = new GlobalSyntheticsTestingConsumer();
paths.add(runD8ForClass(Main.class, mainGlobals, mode));
paths.add(runD8ForClass(TestCallingFoo.class, testCallingFooGlobals, mode));
paths.add(runD8ForClass(TestCallingBar.class, testCallingBarGlobals, mode));
assertFalse(mainGlobals.hasGlobals());
assertFalse(testCallingFooGlobals.hasGlobals());
assertFalse(testCallingBarGlobals.hasGlobals());
testForD8()
.setMode(mode)
.addProgramFiles(paths)
.apply(this::setupTestCompileBuilder)
.addHorizontallyMergedClassesInspector(
inspector -> {
if (isGreaterOrEqualToMockLevel() || mode.isDebug()) {
inspector.assertNoClassesMerged();
} else {
inspector.assertClassReferencesMerged(
SyntheticItemsTestUtils.syntheticApiOutlineClass(TestCallingFoo.class, 0),
SyntheticItemsTestUtils.syntheticApiOutlineClass(TestCallingBar.class, 0));
}
})
.compile()
.inspect(inspector -> inspect(inspector, mode))
.applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
}
private void inspect(CodeInspector inspector, CompilationMode mode) {
ClassSubject libraryClassSubject = inspector.clazz(LibraryClass.class);
if (isGreaterOrEqualToMockLevel()) {
assertThat(libraryClassSubject, isAbsent());
} else {
assertEquals(mode.isRelease() ? 4 : 5, inspector.allClasses().size());
}
}
private void checkOutput(SingleTestRunResult<?> runResult) {
runResult.assertSuccessWithOutputLinesIf(
addToBootClasspath(), "LibraryClass::foo", "LibraryClass::bar");
runResult.assertFailureWithErrorThatThrowsIf(!addToBootClasspath(), NoClassDefFoundError.class);
}
// Only present form api level 23.
public static class LibraryClass {
public static void foo() {
System.out.println("LibraryClass::foo");
}
public static void bar() {
System.out.println("LibraryClass::bar");
}
}
public static class TestCallingFoo {
public static void callFoo() {
LibraryClass.foo();
}
}
public static class TestCallingBar {
public static void callBar() {
LibraryClass.bar();
}
}
public static class Main {
public static void main(String[] args) {
TestCallingFoo.callFoo();
TestCallingBar.callBar();
}
}
}