| // Copyright (c) 2021, 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 records; |
| |
| import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertThrows; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.CompilationFailedException; |
| import com.android.tools.r8.D8TestBuilder; |
| import com.android.tools.r8.D8TestCompileResult; |
| import com.android.tools.r8.GlobalSyntheticsTestingConsumer; |
| import com.android.tools.r8.OutputMode; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.errors.DuplicateTypesDiagnostic; |
| import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import java.nio.file.Path; |
| import org.junit.Assume; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class RecordMergeTest extends TestBase { |
| |
| private static final String EXPECTED_RESULT_1 = |
| StringUtils.lines("BobX", "43", "FelixX", "-1", "print", "Bob43", "extra"); |
| |
| private static final String EXPECTED_RESULT_2 = |
| StringUtils.lines( |
| "Jane Doe", "42", "true", "true", "true", "false", "false", "false", "false"); |
| |
| private final TestParameters parameters; |
| |
| public RecordMergeTest(TestParameters parameters) { |
| this.parameters = parameters; |
| } |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); |
| } |
| |
| @Test |
| public void testNoGlobalSyntheticsConsumer() throws Exception { |
| D8TestBuilder builder = |
| testForD8(parameters.getBackend()) |
| .addStrippedOuter(getClass()) |
| .addProgramClassesAndInnerClasses(RecordWithMembers.class) |
| .addClasspathClassesAndInnerClasses(SimpleRecord.class) |
| .setMinApi(parameters) |
| .setIntermediate(true); |
| if (isRecordsFullyDesugaredForD8(parameters)) { |
| assertThrows( |
| CompilationFailedException.class, |
| () -> |
| builder.compileWithExpectedDiagnostics( |
| diagnostics -> |
| diagnostics |
| .assertOnlyErrors() |
| .assertErrorsMatch( |
| diagnosticType(MissingGlobalSyntheticsConsumerDiagnostic.class)))); |
| } else { |
| builder.compile(); |
| } |
| } |
| |
| @Test |
| public void testMergeDesugaredInputs() throws Exception { |
| testMergeDesugaredInputsDexPerClass(false); |
| } |
| |
| @Test |
| public void testMergeDesugaredInputsDexPerClass() throws Exception { |
| Assume.assumeTrue("CF is already run from the other test", parameters.isDexRuntime()); |
| testMergeDesugaredInputsDexPerClass(true); |
| } |
| |
| private void testMergeDesugaredInputsDexPerClass(boolean filePerClass) throws Exception { |
| GlobalSyntheticsTestingConsumer globals1 = new GlobalSyntheticsTestingConsumer(); |
| Path output1 = |
| testForD8(parameters.getBackend()) |
| .addStrippedOuter(getClass()) |
| .addProgramClassesAndInnerClasses(RecordWithMembers.class) |
| .addClasspathClassesAndInnerClasses(SimpleRecord.class) |
| .setMinApi(parameters) |
| .setIntermediate(true) |
| .applyIf( |
| filePerClass && !parameters.isCfRuntime(), |
| b -> b.setOutputMode(OutputMode.DexFilePerClassFile)) |
| .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1)) |
| .compile() |
| .inspect(this::assertDoesNotHaveRecordTag) |
| .writeToZip(); |
| |
| GlobalSyntheticsTestingConsumer globals2 = new GlobalSyntheticsTestingConsumer(); |
| Path output2 = |
| testForD8(parameters.getBackend()) |
| .addProgramClassesAndInnerClasses(SimpleRecord.class) |
| .addClasspathClassesAndInnerClasses(getClass()) |
| .setMinApi(parameters) |
| .setIntermediate(true) |
| .applyIf( |
| filePerClass && !parameters.isCfRuntime(), |
| b -> b.setOutputMode(OutputMode.DexFilePerClassFile)) |
| .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals2)) |
| .compile() |
| .inspect(this::assertDoesNotHaveRecordTag) |
| .writeToZip(); |
| |
| assertTrue(isRecordsFullyDesugaredForD8(parameters) ^ !globals1.hasGlobals()); |
| assertTrue(isRecordsFullyDesugaredForD8(parameters) ^ !globals2.hasGlobals()); |
| |
| D8TestCompileResult result = |
| testForD8(parameters.getBackend()) |
| .addProgramFiles(output1, output2) |
| .apply( |
| b -> |
| b.getBuilder() |
| .addGlobalSyntheticsResourceProviders(globals1.getProviders()) |
| .addGlobalSyntheticsResourceProviders(globals2.getProviders())) |
| .setMinApi(parameters) |
| .compile() |
| .inspect(this::assertHasRecordTag); |
| |
| result |
| .run(parameters.getRuntime(), RecordWithMembers.class) |
| .applyIf( |
| isRecordsFullyDesugaredForD8(parameters) |
| || runtimeWithRecordsSupport(parameters.getRuntime()), |
| r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1), |
| r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); |
| result |
| .run(parameters.getRuntime(), SimpleRecord.class) |
| .applyIf( |
| isRecordsFullyDesugaredForD8(parameters) |
| || runtimeWithRecordsSupport(parameters.getRuntime()), |
| r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2), |
| r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); |
| } |
| |
| @Test |
| public void testMergeDesugaredAndNonDesugaredInputs() throws Exception { |
| GlobalSyntheticsTestingConsumer globals1 = new GlobalSyntheticsTestingConsumer(); |
| Path output1 = |
| testForD8(parameters.getBackend()) |
| .addProgramClassesAndInnerClasses(RecordWithMembers.class) |
| .addClasspathClasses(getClass()) |
| .addClasspathClassesAndInnerClasses(SimpleRecord.class) |
| .setMinApi(parameters) |
| .setIntermediate(true) |
| .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1)) |
| .compile() |
| .writeToZip(); |
| |
| D8TestCompileResult result = |
| testForD8(parameters.getBackend()) |
| .addStrippedOuter(getClass()) |
| .addProgramFiles(output1) |
| .apply( |
| b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1.getProviders())) |
| .addProgramClassesAndInnerClasses(SimpleRecord.class) |
| .addClasspathClassesAndInnerClasses(getClass()) |
| .setMinApi(parameters) |
| .compile(); |
| result |
| .run(parameters.getRuntime(), RecordWithMembers.class) |
| .applyIf( |
| isRecordsFullyDesugaredForD8(parameters) |
| || runtimeWithRecordsSupport(parameters.getRuntime()), |
| r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1), |
| r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); |
| result |
| .run(parameters.getRuntime(), SimpleRecord.class) |
| .applyIf( |
| isRecordsFullyDesugaredForD8(parameters) |
| || runtimeWithRecordsSupport(parameters.getRuntime()), |
| r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2), |
| r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); |
| } |
| |
| @Test |
| public void testMergeNonIntermediates() throws Exception { |
| Path output1 = |
| testForD8(parameters.getBackend()) |
| .addStrippedOuter(getClass()) |
| .addProgramClassesAndInnerClasses(RecordWithMembers.class) |
| .addClasspathClassesAndInnerClasses(SimpleRecord.class) |
| .setMinApi(parameters) |
| .compile() |
| .inspect(this::assertHasRecordTag) |
| .writeToZip(); |
| |
| Path output2 = |
| testForD8(parameters.getBackend()) |
| .addProgramClassesAndInnerClasses(SimpleRecord.class) |
| .addClasspathClassesAndInnerClasses(getClass()) |
| .setMinApi(parameters) |
| .compile() |
| .inspect(this::assertHasRecordTag) |
| .writeToZip(); |
| |
| if (!isRecordsFullyDesugaredForD8(parameters)) { |
| D8TestCompileResult result = |
| testForD8(parameters.getBackend()) |
| .addProgramFiles(output1, output2) |
| .setMinApi(parameters) |
| .compile(); |
| result |
| .run(parameters.getRuntime(), RecordWithMembers.class) |
| .applyIf( |
| isRecordsFullyDesugaredForD8(parameters) |
| || runtimeWithRecordsSupport(parameters.getRuntime()), |
| r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1), |
| r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); |
| result |
| .run(parameters.getRuntime(), SimpleRecord.class) |
| .applyIf( |
| isRecordsFullyDesugaredForD8(parameters) |
| || runtimeWithRecordsSupport(parameters.getRuntime()), |
| r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2), |
| r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); |
| } else { |
| assertThrows( |
| CompilationFailedException.class, |
| () -> |
| testForD8(parameters.getBackend()) |
| .addProgramFiles(output1, output2) |
| .setMinApi(parameters) |
| .compileWithExpectedDiagnostics( |
| diagnostics -> |
| diagnostics |
| .assertOnlyErrors() |
| .assertErrorsMatch(diagnosticType(DuplicateTypesDiagnostic.class)))); |
| } |
| } |
| |
| private void assertHasRecordTag(CodeInspector inspector) { |
| // Note: this should be asserting on record tag. |
| assertThat( |
| inspector.clazz("java.lang.Record"), isPresentIf(isRecordsFullyDesugaredForD8(parameters))); |
| } |
| |
| private void assertDoesNotHaveRecordTag(CodeInspector inspector) { |
| // Note: this should be asserting on record tag. |
| assertThat(inspector.clazz("java.lang.Record"), isAbsent()); |
| } |
| |
| public class RecordWithMembers { |
| record PersonWithConstructors(String name, int age) { |
| |
| public PersonWithConstructors(String name, int age) { |
| this.name = name + "X"; |
| this.age = age; |
| } |
| |
| public PersonWithConstructors(String name) { |
| this(name, -1); |
| } |
| } |
| |
| record PersonWithMethods(String name, int age) { |
| public static void staticPrint() { |
| System.out.println("print"); |
| } |
| |
| @Override |
| public String toString() { |
| return name + age; |
| } |
| } |
| |
| record PersonWithFields(String name, int age) { |
| |
| // Extra instance fields are not allowed on records. |
| public static String globalName; |
| } |
| |
| public static void main(String[] args) { |
| personWithConstructorTest(); |
| personWithMethodsTest(); |
| personWithFieldsTest(); |
| } |
| |
| private static void personWithConstructorTest() { |
| PersonWithConstructors bob = new PersonWithConstructors("Bob", 43); |
| System.out.println(bob.name()); |
| System.out.println(bob.age()); |
| PersonWithConstructors felix = new PersonWithConstructors("Felix"); |
| System.out.println(felix.name()); |
| System.out.println(felix.age()); |
| } |
| |
| private static void personWithMethodsTest() { |
| PersonWithMethods.staticPrint(); |
| PersonWithMethods bob = new PersonWithMethods("Bob", 43); |
| System.out.println(bob.toString()); |
| } |
| |
| private static void personWithFieldsTest() { |
| PersonWithFields.globalName = "extra"; |
| System.out.println(PersonWithFields.globalName); |
| } |
| } |
| |
| public class SimpleRecord { |
| |
| record Person(String name, int age) {} |
| |
| public static void main(String[] args) { |
| Person janeDoe = new Person("Jane Doe", 42); |
| System.out.println(janeDoe.name()); |
| System.out.println(janeDoe.age()); |
| |
| // Test equals with self. |
| System.out.println(janeDoe.equals(janeDoe)); |
| |
| // Test equals with structurally equals Person. |
| Person otherJaneDoe = new Person("Jane Doe", 42); |
| System.out.println(janeDoe.equals(otherJaneDoe)); |
| System.out.println(otherJaneDoe.equals(janeDoe)); |
| |
| // Test equals with not-structually equals Person. |
| Person johnDoe = new Person("John Doe", 42); |
| System.out.println(janeDoe.equals(johnDoe)); |
| System.out.println(johnDoe.equals(janeDoe)); |
| |
| // Test equals with Object and null. |
| System.out.println(janeDoe.equals(new Object())); |
| System.out.println(janeDoe.equals(null)); |
| } |
| } |
| } |