Records: initial tests
- Tests for simple record cases
- Warning for empty records
- Support ASM ACC_RECORD flag for empty record support
Bug: 169645628
Change-Id: I83c7a091893c40241ad6d3b90b7ae5fefaa18e2f
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index dfa8667..8ea7319 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -135,6 +135,7 @@
public static final int ACC_ANNOTATION = 0x2000;
public static final int ACC_ENUM = 0x4000;
public static final int ACC_CONSTRUCTOR = 0x10000;
+ public static final int ACC_RECORD = 0x10000;
public static final int ACC_DECLARED_SYNCHRONIZED = 0x20000;
public static final String JAVA_LANG_OBJECT_NAME = "java/lang/Object";
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 7998171..abeb404 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -22,9 +22,7 @@
private static final int DEX_FLAGS
= SHARED_FLAGS;
- private static final int CF_FLAGS
- = SHARED_FLAGS
- | Constants.ACC_SUPER;
+ private static final int CF_FLAGS = SHARED_FLAGS | Constants.ACC_SUPER | Constants.ACC_RECORD;
@Override
protected List<String> getNames() {
@@ -35,6 +33,7 @@
.add("annotation")
.add("enum")
.add("super")
+ .add("record")
.build();
}
@@ -47,6 +46,7 @@
.add(this::isAnnotation)
.add(this::isEnum)
.add(this::isSuper)
+ .add(this::isRecord)
.build();
}
@@ -178,6 +178,14 @@
set(Constants.ACC_ENUM);
}
+ public boolean isRecord() {
+ return isSet(Constants.ACC_RECORD);
+ }
+
+ public void setRecord() {
+ set(Constants.ACC_RECORD);
+ }
+
public boolean isSuper() {
return isSet(Constants.ACC_SUPER);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 4cb631b..18b0434 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -213,6 +213,7 @@
public final DexString stringDescriptor = createString("Ljava/lang/String;");
public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;");
public final DexString objectDescriptor = createString("Ljava/lang/Object;");
+ public final DexString recordDescriptor = createString("Ljava/lang/Record;");
public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
public final DexString classDescriptor = createString("Ljava/lang/Class;");
public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
@@ -342,6 +343,7 @@
public final DexType stringType = createStaticallyKnownType(stringDescriptor);
public final DexType stringArrayType = createStaticallyKnownType(stringArrayDescriptor);
public final DexType objectType = createStaticallyKnownType(objectDescriptor);
+ public final DexType recordType = createStaticallyKnownType(recordDescriptor);
public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor);
public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
public final DexType enumType = createStaticallyKnownType(enumDescriptor);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 40b6445..1f67c4d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -301,7 +301,8 @@
@Override
public RecordComponentVisitor visitRecordComponent(
String name, String descriptor, String signature) {
- throw new CompilationError("Records are not supported", origin);
+ // TODO(b/169645628): Support Records.
+ return super.visitRecordComponent(name, descriptor, signature);
}
@Override
@@ -326,6 +327,12 @@
}
this.deprecated = AsmUtils.isDeprecated(access);
accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
+ if (accessFlags.isRecord()) {
+ // TODO(b/169645628): Support records in all compilation.
+ if (!application.options.canUseRecords()) {
+ throw new CompilationError("Records are not supported", origin);
+ }
+ }
type = application.getTypeFromName(name);
// Check if constraints from
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 240d3b9..07bdb1b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -485,6 +485,11 @@
return !canUseNestBasedAccess();
}
+ public boolean canUseRecords() {
+ // TODO(b/169645628): Replace by true when records are supported.
+ return testing.canUseRecords;
+ }
+
public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
public Set<String> extensiveInterfaceMethodMinifierLoggingFilter =
getExtensiveInterfaceMethodMinifierLoggingFilter();
@@ -1281,6 +1286,7 @@
public boolean enableSwitchToIfRewriting = true;
public boolean enableEnumUnboxingDebugLogs = false;
public boolean forceRedundantConstNumberRemoval = false;
+ public boolean canUseRecords = false;
public boolean invertConditionals = false;
public boolean placeExceptionalBlocksLast = false;
public boolean dontCreateMarkerInD8 = false;
diff --git a/src/test/examplesJava15/records/Main.java b/src/test/examplesJava15/records/EmptyRecord.java
similarity index 60%
copy from src/test/examplesJava15/records/Main.java
copy to src/test/examplesJava15/records/EmptyRecord.java
index 7be696d..5d16e44 100644
--- a/src/test/examplesJava15/records/Main.java
+++ b/src/test/examplesJava15/records/EmptyRecord.java
@@ -4,13 +4,11 @@
package records;
-public class Main {
+public class EmptyRecord {
- record Person(String name, int age) {}
+ record Empty() {}
public static void main(String[] args) {
- Person janeDoe = new Person("Jane Doe", 42);
- System.out.println(janeDoe.name);
- System.out.println(janeDoe.age);
+ System.out.println(new Empty());
}
}
diff --git a/src/test/examplesJava15/records/RecordInstanceOf.java b/src/test/examplesJava15/records/RecordInstanceOf.java
new file mode 100644
index 0000000..591192c
--- /dev/null
+++ b/src/test/examplesJava15/records/RecordInstanceOf.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, 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;
+
+public class RecordInstanceOf {
+
+ record Empty() {}
+
+ record Person(String name, int age) {}
+
+ public static void main(String[] args) {
+ Empty empty = new Empty();
+ Person janeDoe = new Person("Jane Doe", 42);
+ Object o = new Object();
+ System.out.println(janeDoe instanceof java.lang.Record);
+ System.out.println(empty instanceof java.lang.Record);
+ System.out.println(o instanceof java.lang.Record);
+ }
+}
diff --git a/src/test/examplesJava15/records/RecordInvokeCustom.java b/src/test/examplesJava15/records/RecordInvokeCustom.java
new file mode 100644
index 0000000..c460f56
--- /dev/null
+++ b/src/test/examplesJava15/records/RecordInvokeCustom.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2020, 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;
+
+public class RecordInvokeCustom {
+
+ record Empty() {}
+
+ record Person(String name, int age) {}
+
+ public static void main(String[] args) {
+ emptyTest();
+ equalityTest();
+ toStringTest();
+ }
+
+ private static void emptyTest() {
+ Empty empty1 = new Empty();
+ Empty empty2 = new Empty();
+ System.out.println(empty1.toString());
+ System.out.println(empty1.equals(empty2));
+ System.out.println(empty1.hashCode() == empty2.hashCode());
+ System.out.println(empty1.toString().equals(empty2.toString()));
+ }
+
+ private static void toStringTest() {
+ Person janeDoe = new Person("Jane Doe", 42);
+ System.out.println(janeDoe.toString());
+ }
+
+ private static void equalityTest() {
+ Person jane1 = new Person("Jane Doe", 42);
+ Person jane2 = new Person("Jane Doe", 42);
+ String nonIdenticalString = "Jane " + (System.currentTimeMillis() > 0 ? "Doe" : "Zan");
+ Person jane3 = new Person(nonIdenticalString, 42);
+ Person bob = new Person("Bob", 42);
+ Person youngJane = new Person("Jane Doe", 22);
+ System.out.println(jane1.equals(jane2));
+ System.out.println(jane1.toString().equals(jane2.toString()));
+ System.out.println(nonIdenticalString == "Jane Doe"); // false.
+ System.out.println(nonIdenticalString.equals("Jane Doe")); // true.
+ System.out.println(jane1.equals(jane3));
+ System.out.println(jane1.equals(bob));
+ System.out.println(jane1.equals(youngJane));
+ }
+}
diff --git a/src/test/examplesJava15/records/RecordReflection.java b/src/test/examplesJava15/records/RecordReflection.java
new file mode 100644
index 0000000..1b94a12
--- /dev/null
+++ b/src/test/examplesJava15/records/RecordReflection.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2020, 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 java.util.Arrays;
+
+public class RecordReflection {
+
+ record Empty(){}
+
+ record Person(String name, int age) {}
+
+ record PersonGeneric <S extends CharSequence>(S name, int age) {}
+
+ public static void main(String[] args) {
+ System.out.println(Empty.class.isRecord());
+ System.out.println(Arrays.toString(Empty.class.getRecordComponents()));
+ System.out.println(Person.class.isRecord());
+ System.out.println(Arrays.toString(Person.class.getRecordComponents()));
+ System.out.println(PersonGeneric.class.isRecord());
+ System.out.println(Arrays.toString(PersonGeneric.class.getRecordComponents()));
+ System.out.println(Arrays.toString(PersonGeneric.class.getTypeParameters()));
+ System.out.println(Object.class.isRecord());
+ System.out.println(Arrays.toString(Object.class.getRecordComponents()));
+ }
+
+}
diff --git a/src/test/examplesJava15/records/RecordWithMembers.java b/src/test/examplesJava15/records/RecordWithMembers.java
new file mode 100644
index 0000000..6de2b99
--- /dev/null
+++ b/src/test/examplesJava15/records/RecordWithMembers.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2020, 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;
+
+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);
+ 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);
+ 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);
+ }
+}
diff --git a/src/test/examplesJava15/records/Main.java b/src/test/examplesJava15/records/SimpleRecord.java
similarity index 80%
rename from src/test/examplesJava15/records/Main.java
rename to src/test/examplesJava15/records/SimpleRecord.java
index 7be696d..1f8fca4 100644
--- a/src/test/examplesJava15/records/Main.java
+++ b/src/test/examplesJava15/records/SimpleRecord.java
@@ -4,7 +4,7 @@
package records;
-public class Main {
+public class SimpleRecord {
record Person(String name, int age) {}
@@ -12,5 +12,7 @@
Person janeDoe = new Person("Jane Doe", 42);
System.out.println(janeDoe.name);
System.out.println(janeDoe.age);
+ System.out.println(janeDoe.name());
+ System.out.println(janeDoe.age());
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
new file mode 100644
index 0000000..92183a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -0,0 +1,45 @@
+// 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 com.android.tools.r8.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EmptyRecordTest extends TestBase {
+
+ private static final String RECORD_NAME = "EmptyRecord";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT = StringUtils.lines("Empty[]");
+
+ private final TestParameters parameters;
+
+ public EmptyRecordTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+ return buildParameters(
+ getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addVmArguments("--enable-preview")
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
new file mode 100644
index 0000000..e936a33
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
@@ -0,0 +1,111 @@
+// 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 com.android.tools.r8.desugar.records;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Remove this test when Records are supported by default. */
+@RunWith(Parameterized.class)
+public class InvalidRecordAttributeTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final Backend backend;
+
+ private static final String EMPTY_RECORD = "EmptyRecord";
+ private static final byte[][] EMPTY_RECORD_PROGRAM_DATA =
+ RecordTestUtils.getProgramData(EMPTY_RECORD);
+ private static final String SIMPLE_RECORD = "SimpleRecord";
+ private static final byte[][] SIMPLE_RECORD_PROGRAM_DATA =
+ RecordTestUtils.getProgramData(SIMPLE_RECORD);
+
+ @Parameters(name = "{0} back: {1}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+ return buildParameters(
+ getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build(),
+ Backend.values());
+ }
+
+ public InvalidRecordAttributeTest(TestParameters parameters, Backend backend) {
+ this.parameters = parameters;
+ this.backend = backend;
+ }
+
+ @Test
+ public void testD8EmptyRecord() throws Exception {
+ Assume.assumeTrue(backend.isDex());
+ assertThrows(
+ CompilationFailedException.class,
+ () -> {
+ testForD8(backend)
+ .addProgramClassFileData(EMPTY_RECORD_PROGRAM_DATA)
+ .setMinApi(AndroidApiLevel.B)
+ .compileWithExpectedDiagnostics(
+ InvalidRecordAttributeTest::assertUnsupportedRecordError);
+ });
+ }
+
+ @Test
+ public void testD8SimpleRecord() throws Exception {
+ Assume.assumeTrue(backend.isDex());
+ assertThrows(
+ CompilationFailedException.class,
+ () -> {
+ testForD8(backend)
+ .addProgramClassFileData(RecordTestUtils.getProgramData(SIMPLE_RECORD))
+ .setMinApi(AndroidApiLevel.B)
+ .compileWithExpectedDiagnostics(
+ InvalidRecordAttributeTest::assertUnsupportedRecordError);
+ });
+ }
+
+ @Test
+ public void testR8EmptyRecord() throws Exception {
+ assertThrows(
+ CompilationFailedException.class,
+ () -> {
+ testForR8(backend)
+ .addProgramClassFileData(EMPTY_RECORD_PROGRAM_DATA)
+ .setMinApi(AndroidApiLevel.B)
+ .addKeepMainRule(RecordTestUtils.getMainType(EMPTY_RECORD))
+ .compileWithExpectedDiagnostics(
+ InvalidRecordAttributeTest::assertUnsupportedRecordError);
+ });
+ }
+
+ @Test
+ public void testR8SimpleRecord() throws Exception {
+ assertThrows(
+ CompilationFailedException.class,
+ () -> {
+ testForR8(backend)
+ .addProgramClassFileData(SIMPLE_RECORD_PROGRAM_DATA)
+ .setMinApi(AndroidApiLevel.B)
+ .addKeepMainRule(RecordTestUtils.getMainType(SIMPLE_RECORD))
+ .compileWithExpectedDiagnostics(
+ InvalidRecordAttributeTest::assertUnsupportedRecordError);
+ });
+ }
+
+ private static void assertUnsupportedRecordError(TestDiagnosticMessages diagnostics) {
+ diagnostics.assertErrorThatMatches(
+ diagnosticMessage(containsString("Records are not supported")));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
new file mode 100644
index 0000000..33a3b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -0,0 +1,45 @@
+// 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 com.android.tools.r8.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordInstanceOfTest extends TestBase {
+
+ private static final String RECORD_NAME = "RecordInstanceOf";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT = StringUtils.lines("true", "true", "false");
+
+ private final TestParameters parameters;
+
+ public RecordInstanceOfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+ return buildParameters(
+ getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addVmArguments("--enable-preview")
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
new file mode 100644
index 0000000..1d1e785
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -0,0 +1,58 @@
+// 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 com.android.tools.r8.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordInvokeCustomTest extends TestBase {
+
+ private static final String RECORD_NAME = "RecordInvokeCustom";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "Empty[]",
+ "true",
+ "true",
+ "true",
+ "true",
+ "true",
+ "false",
+ "true",
+ "true",
+ "false",
+ "false",
+ "Person[name=Jane Doe, age=42]");
+
+ private final TestParameters parameters;
+
+ public RecordInvokeCustomTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+ return buildParameters(
+ getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addVmArguments("--enable-preview")
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
new file mode 100644
index 0000000..1bb0a1a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -0,0 +1,55 @@
+// 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 com.android.tools.r8.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordReflectionTest extends TestBase {
+
+ private static final String RECORD_NAME = "RecordReflection";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "true",
+ "[]",
+ "true",
+ "[java.lang.String name, int age]",
+ "true",
+ "[java.lang.CharSequence name, int age]",
+ "[S]",
+ "false",
+ "null");
+
+ private final TestParameters parameters;
+
+ public RecordReflectionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+ return buildParameters(
+ getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addVmArguments("--enable-preview")
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
new file mode 100644
index 0000000..d4a804b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -0,0 +1,71 @@
+// 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 com.android.tools.r8.desugar.records;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Records are compiled using: third_party/openjdk/jdk-15/linux/bin/javac --release 15
+ * --enable-preview path/to/file.java
+ */
+public class RecordTestUtils {
+
+ private static final String EXAMPLE_FOLDER = "examplesJava15";
+ private static final String RECORD_FOLDER = "records";
+
+ public static Path jar() {
+ return Paths.get(ToolHelper.TESTS_BUILD_DIR, EXAMPLE_FOLDER, RECORD_FOLDER + ".jar");
+ }
+
+ public static byte[][] getProgramData(String mainClassSimpleName) {
+ byte[][] bytes = classDataFromPrefix(RECORD_FOLDER + "/" + mainClassSimpleName);
+ assert bytes.length > 0 : "Did not find any program data for " + mainClassSimpleName;
+ return bytes;
+ }
+
+ public static String getMainType(String mainClassSimpleName) {
+ return RECORD_FOLDER + "." + mainClassSimpleName;
+ }
+
+ private static byte[][] classDataFromPrefix(String prefix) {
+ Path examplePath = jar();
+ if (!Files.exists(examplePath)) {
+ throw new RuntimeException(
+ "Could not find path "
+ + examplePath
+ + ". Build "
+ + EXAMPLE_FOLDER
+ + " by running tools/gradle.py build"
+ + StringUtils.capitalize(EXAMPLE_FOLDER));
+ }
+ List<byte[]> result = new ArrayList<>();
+ try (ZipFile zipFile = new ZipFile(examplePath.toFile())) {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry zipEntry = entries.nextElement();
+ if (zipEntry.getName().startsWith(prefix)) {
+ result.add(ByteStreams.toByteArray(zipFile.getInputStream(zipEntry)));
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Could not read zip-entry from " + examplePath.toString(), e);
+ }
+ if (result.isEmpty()) {
+ throw new RuntimeException("Did not find any class with prefix " + prefix);
+ }
+ return result.toArray(new byte[0][0]);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
new file mode 100644
index 0000000..c0ca829
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -0,0 +1,47 @@
+// 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 com.android.tools.r8.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordWithMembersTest extends TestBase {
+
+ private static final String RECORD_NAME = "RecordWithMembers";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "BobX", "43", "BobX", "43", "FelixX", "-1", "FelixX", "-1", "print", "Bob43", "extra");
+
+ private final TestParameters parameters;
+
+ public RecordWithMembersTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+ return buildParameters(
+ getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addVmArguments("--enable-preview")
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordsAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordsAttributeTest.java
deleted file mode 100644
index 831c426..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordsAttributeTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2020, 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.desugar.records;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.examples.jdk15.Records;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.List;
-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 RecordsAttributeTest extends TestBase {
-
- private final Backend backend;
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
- return buildParameters(
- getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build(),
- Backend.values());
- }
-
- public RecordsAttributeTest(TestParameters parameters, Backend backend) {
- this.parameters = parameters;
- this.backend = backend;
- }
-
- @Test
- public void testJvm() throws Exception {
- assumeFalse(parameters.isNoneRuntime());
- assumeTrue(backend == Backend.CF);
- testForJvm()
- .addRunClasspathFiles(Records.jar())
- .addVmArguments("--enable-preview")
- .run(parameters.getRuntime(), Records.Main.typeName())
- .assertSuccessWithOutputLines("Jane Doe", "42");
- }
-
- @Test
- public void testD8() throws Exception {
- assertThrows(
- CompilationFailedException.class,
- () -> {
- testForD8(backend)
- .addProgramClassFileData(Records.Main.bytes(), Records.Main$Person.bytes())
- .setMinApi(AndroidApiLevel.B)
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertErrorThatMatches(
- diagnosticMessage(containsString("Records are not supported")));
- });
- });
- }
-
- @Test
- public void testR8() throws Exception {
- assertThrows(
- CompilationFailedException.class,
- () -> {
- testForR8(backend)
- .addProgramClassFileData(Records.Main.bytes(), Records.Main$Person.bytes())
- .setMinApi(AndroidApiLevel.B)
- .addKeepMainRule(Records.Main.typeName())
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertErrorThatMatches(
- diagnosticMessage(containsString("Records are not supported")));
- });
- });
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
new file mode 100644
index 0000000..35ce924
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -0,0 +1,46 @@
+// 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 com.android.tools.r8.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SimpleRecordTest extends TestBase {
+
+ private static final String RECORD_NAME = "SimpleRecord";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
+
+ private final TestParameters parameters;
+
+ public SimpleRecordTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+ return buildParameters(
+ getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addVmArguments("--enable-preview")
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}