Merge commit '7a230461925d65a826100945f01a8b6f86ab3e66' into dev-release

Change-Id: Icae05d3e143368fcd9da4475dca8429568a1ae12
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 10f0699..cee4f28 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -152,12 +151,7 @@
     DexString descriptor = annotationType.getDescriptor();
     if (descriptor.startsWith(factory.dalvikAnnotationPrefix)) {
       if (descriptor.startsWith(factory.dalvikAnnotationCodegenCovariantReturnTypePrefix)) {
-        if (options.processCovariantReturnTypeAnnotations) {
-          return true;
-        }
-        throw options.reporter.fatalError(
-            new StringDiagnostic(
-                "Unexpected @CovariantReturnType annotation in non-platform build"));
+        return options.processCovariantReturnTypeAnnotations;
       }
       return descriptor.startsWith(factory.dalvikAnnotationOptimizationPrefix);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index adb0992..9ce000f 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -70,6 +70,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -649,14 +650,17 @@
   private void dumpAndroidResourcesProvider(
       AndroidResourceProvider androidResourceProvider, ZipOutputStream out, String name)
       throws IOException, ResourceException {
+    Set<String> seen = new HashSet<>();
     try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
       try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
         for (AndroidResourceInput androidResource : androidResourceProvider.getAndroidResources()) {
-          writeToZipStream(
-              archiveOutputStream,
-              androidResource.getPath().location(),
-              androidResource.getByteStream().readAllBytes(),
-              ZipEntry.DEFLATED);
+          if (seen.add(androidResource.getPath().location())) {
+            writeToZipStream(
+                archiveOutputStream,
+                androidResource.getPath().location(),
+                androidResource.getByteStream().readAllBytes(),
+                ZipEntry.DEFLATED);
+          }
         }
       }
       writeToZipStream(out, name, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java b/src/test/examplesJava17/records/RecordBlogTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
rename to src/test/examplesJava17/records/RecordBlogTest.java
index 10f10f4..1128f07 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
+++ b/src/test/examplesJava17/records/RecordBlogTest.java
@@ -2,11 +2,12 @@
 // 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;
+package records;
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -26,9 +27,6 @@
 @RunWith(Parameterized.class)
 public class RecordBlogTest extends TestBase {
 
-  private static final String RECORD_NAME = "RecordBlog";
-  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
-  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
   private static final String REFERENCE_OUTPUT_FORMAT = "Person[name=%s, age=42]";
   private static final String CLASS = "records.RecordBlog$Person";
   private static final Map<String, String> KEEP_RULE_TO_OUTPUT_FORMAT =
@@ -71,17 +69,17 @@
   public void testReference() throws Exception {
     assumeTrue(isCfRuntimeWithNativeRecordSupport());
     testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .addProgramClassesAndInnerClasses(RecordBlog.class)
+        .run(parameters.getRuntime(), RecordBlog.class)
         .assertSuccessWithOutput(computeOutput(REFERENCE_OUTPUT_FORMAT));
   }
 
   @Test
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
+        .addProgramClassesAndInnerClasses(RecordBlog.class)
         .setMinApi(parameters)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordBlog.class)
         .applyIf(
             isRecordsFullyDesugaredForD8(parameters)
                 || runtimeWithRecordsSupport(parameters.getRuntime()),
@@ -99,19 +97,19 @@
           try {
             R8FullTestBuilder builder =
                 testForR8(parameters.getBackend())
-                    .addProgramClassFileData(PROGRAM_DATA)
+                    .addProgramClassesAndInnerClasses(RecordBlog.class)
                     .setMinApi(parameters)
                     .addKeepRules(kr)
-                    .addKeepMainRule(MAIN_TYPE);
+                    .addKeepMainRule(RecordBlog.class);
             String res;
             if (parameters.isCfRuntime()) {
               res =
                   builder
-                      .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-                      .run(parameters.getRuntime(), MAIN_TYPE)
+                      .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+                      .run(parameters.getRuntime(), RecordBlog.class)
                       .getStdOut();
             } else {
-              res = builder.run(parameters.getRuntime(), MAIN_TYPE).getStdOut();
+              res = builder.run(parameters.getRuntime(), RecordBlog.class).getStdOut();
             }
             results.put(kr, res);
           } catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java b/src/test/examplesJava17/records/RecordClasspathTest.java
similarity index 60%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java
rename to src/test/examplesJava17/records/RecordClasspathTest.java
index a431cf2..867d632 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java
+++ b/src/test/examplesJava17/records/RecordClasspathTest.java
@@ -2,21 +2,27 @@
 // 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;
+package records;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.util.List;
 import org.junit.Assume;
 import org.junit.Test;
@@ -30,8 +36,6 @@
 @RunWith(Parameterized.class)
 public class RecordClasspathTest extends TestBase {
 
-  private static final String RECORD_NAME_1 = "RecordWithMembers";
-  private static final byte[][] PROGRAM_DATA_1 = RecordTestUtils.getProgramData(RECORD_NAME_1);
   private static final String EXPECTED_RESULT = StringUtils.lines("Hello");
 
   private final TestParameters parameters;
@@ -53,8 +57,19 @@
         BooleanUtils.values());
   }
 
-  private byte[][] getClasspathData() {
-    return stripClasspath ? stripFields(PROGRAM_DATA_1) : PROGRAM_DATA_1;
+  private byte[][] getClasspathData() throws IOException {
+    byte[][] programData =
+        ToolHelper.getClassFilesForInnerClasses(getClass()).stream()
+            .map(
+                c -> {
+                  try {
+                    return Files.readAllBytes(c);
+                  } catch (IOException e) {
+                    throw new RuntimeException(e);
+                  }
+                })
+            .toArray(byte[][]::new);
+    return stripClasspath ? stripFields(programData) : programData;
   }
 
   private byte[][] stripFields(byte[][] programData1) {
@@ -80,6 +95,7 @@
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
+        .addClasspathClasses(getClass())
         .addClasspathClassFileData(getClasspathData())
         .setMinApi(parameters)
         .compile()
@@ -94,6 +110,7 @@
     Assume.assumeFalse(parameters.isCfRuntime());
     testForD8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
+        .addClasspathClasses(getClass())
         .addClasspathClassFileData(getClasspathData())
         .setMinApi(parameters)
         .setIntermediate(true)
@@ -112,12 +129,20 @@
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
             .addProgramClasses(TestClass.class)
+            .addClasspathClassFileData(
+                TestBase.transformer(getClass())
+                    .removeFields(FieldPredicate.all())
+                    .removeMethods(MethodPredicate.all())
+                    .removeAllAnnotations()
+                    .setSuper(descriptor(Object.class))
+                    .setImplements()
+                    .transform())
             .addClasspathClassFileData(getClasspathData())
             .setMinApi(parameters)
             .addKeepMainRule(TestClass.class);
     if (parameters.isCfRuntime()) {
       builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
           .compile()
           .inspect(this::assertNoRecord)
           .inspect(RecordTestUtils::assertRecordsAreRecords)
@@ -140,4 +165,62 @@
       System.out.println("Hello");
     }
   }
+
+  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);
+    }
+  }
 }
diff --git a/src/test/examplesJava17/records/RecordComponentAnnotationsTest.java b/src/test/examplesJava17/records/RecordComponentAnnotationsTest.java
new file mode 100644
index 0000000..8aba71c
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordComponentAnnotationsTest.java
@@ -0,0 +1,476 @@
+// 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 records;
+
+import static com.android.tools.r8.utils.codeinspector.AnnotationMatchers.hasAnnotationTypes;
+import static com.android.tools.r8.utils.codeinspector.AnnotationMatchers.hasElements;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Field;
+import java.lang.reflect.RecordComponent;
+import java.util.ArrayList;
+import java.util.Comparator;
+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 RecordComponentAnnotationsTest extends TestBase {
+
+  private static final String JVM_UNTIL_20_EXPECTED_RESULT =
+      StringUtils.lines(
+          "Jane Doe",
+          "42",
+          "true",
+          "2",
+          "name",
+          "java.lang.String",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"a\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(\"c\")",
+          "age",
+          "int",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"x\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(\"z\")",
+          "2",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"x\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationFieldOnly(\"y\")",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"a\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationFieldOnly(\"b\")");
+  private static final String JVM_FROM_21_EXPECTED_RESULT =
+      JVM_UNTIL_20_EXPECTED_RESULT.replaceAll(
+          "records.RecordComponentAnnotationsTest\\$Annotation",
+          "records.RecordComponentAnnotationsTest.Annotation");
+  private static final String ART_EXPECTED_RESULT =
+      StringUtils.lines(
+          "Jane Doe",
+          "42",
+          "true",
+          "2",
+          "name",
+          "java.lang.String",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=a)",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(value=c)",
+          "age",
+          "int",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=x)",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(value=z)",
+          "2",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=x)",
+          "@records.RecordComponentAnnotationsTest$AnnotationFieldOnly(value=y)",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=a)",
+          "@records.RecordComponentAnnotationsTest$AnnotationFieldOnly(value=b)");
+  private static final String JVM_EXPECTED_RESULT_R8 =
+      StringUtils.lines(
+          "Jane Doe",
+          "42",
+          "true",
+          "2",
+          "a",
+          "java.lang.String",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"a\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(\"c\")",
+          "b",
+          "int",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"x\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(\"z\")",
+          "2",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"a\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationFieldOnly(\"b\")",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"x\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationFieldOnly(\"y\")");
+  private static final String ART_EXPECTED_RESULT_R8 =
+      StringUtils.lines(
+          "Jane Doe",
+          "42",
+          "true",
+          "2",
+          "a",
+          "java.lang.String",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=a)",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(value=c)",
+          "b",
+          "int",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=x)",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(value=z)",
+          "2",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=a)",
+          "@records.RecordComponentAnnotationsTest$AnnotationFieldOnly(value=b)",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=x)",
+          "@records.RecordComponentAnnotationsTest$AnnotationFieldOnly(value=y)");
+  private static final String JVM_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS =
+      StringUtils.lines(
+          "Jane Doe",
+          "42",
+          "true",
+          "2",
+          "a",
+          "java.lang.String",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"a\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(\"c\")",
+          "b",
+          "int",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(\"x\")",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(\"z\")",
+          "2",
+          "0",
+          "0");
+  private static final String ART_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS =
+      StringUtils.lines(
+          "Jane Doe",
+          "42",
+          "true",
+          "2",
+          "a",
+          "java.lang.String",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=a)",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(value=c)",
+          "b",
+          "int",
+          "true",
+          "2",
+          "@records.RecordComponentAnnotationsTest$Annotation(value=x)",
+          "@records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly(value=z)",
+          "2",
+          "0",
+          "0");
+  private static final String EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT =
+      StringUtils.lines("Jane Doe", "42", "false");
+  private static final String EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT =
+      StringUtils.lines("Jane Doe", "42", "Class.isRecord not present");
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public Boolean keepAnnotations;
+
+  // Enable once records are no longer partially desugared on platform.
+  public boolean recordDesugaringIsOffOnDex = false;
+
+  @Parameters(name = "{0}, keepAnnotations: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntimesAndAllApiLevels()
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+            .withAllApiLevelsAlsoForCf()
+            .build(),
+        BooleanUtils.values());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    assumeTrue(keepAnnotations);
+    testForJvm(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), RecordWithAnnotations.class)
+        .assertSuccessWithOutput(
+            parameters.getRuntime().asCf().getVm().isLessThanOrEqualTo(CfVm.JDK20)
+                ? JVM_UNTIL_20_EXPECTED_RESULT
+                : JVM_FROM_21_EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    parameters.assumeDexRuntime();
+    assumeTrue(keepAnnotations);
+    testForDesugaring(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), RecordWithAnnotations.class)
+        .applyIf(
+            parameters.isDexRuntime(),
+            r ->
+                r.assertSuccessWithOutput(
+                    runtimeWithRecordsSupport(parameters.getRuntime())
+                        ? EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT
+                        : EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT),
+            r ->
+                r.assertSuccessWithOutput(ART_EXPECTED_RESULT)
+                    .inspect(
+                        inspector -> {
+                          ClassSubject person =
+                              inspector.clazz("records.RecordComponentAnnotationsTest$Person");
+                          FieldSubject name = person.uniqueFieldWithOriginalName("name");
+                          assertThat(name, isPresentAndNotRenamed());
+                          FieldSubject age = person.uniqueFieldWithOriginalName("age");
+                          assertThat(age, isPresentAndNotRenamed());
+                          assertEquals(2, person.getFinalRecordComponents().size());
+
+                          assertEquals(
+                              name.getFinalName(),
+                              person.getFinalRecordComponents().get(0).getName());
+                          assertTrue(
+                              person
+                                  .getFinalRecordComponents()
+                                  .get(0)
+                                  .getType()
+                                  .is("java.lang.String"));
+                          assertNull(person.getFinalRecordComponents().get(0).getSignature());
+                          assertEquals(
+                              2, person.getFinalRecordComponents().get(0).getAnnotations().size());
+                          assertThat(
+                              person.getFinalRecordComponents().get(0).getAnnotations(),
+                              hasAnnotationTypes(
+                                  inspector.getTypeSubject(
+                                      "records.RecordComponentAnnotationsTest$Annotation"),
+                                  inspector.getTypeSubject(
+                                      "records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly")));
+                          assertThat(
+                              person.getFinalRecordComponents().get(0).getAnnotations().get(0),
+                              hasElements(new Pair<>("value", "a")));
+                          assertThat(
+                              person.getFinalRecordComponents().get(0).getAnnotations().get(1),
+                              hasElements(new Pair<>("value", "c")));
+
+                          assertEquals(
+                              age.getFinalName(),
+                              person.getFinalRecordComponents().get(1).getName());
+                          assertTrue(person.getFinalRecordComponents().get(1).getType().is("int"));
+                          assertNull(person.getFinalRecordComponents().get(1).getSignature());
+                          assertEquals(
+                              2, person.getFinalRecordComponents().get(1).getAnnotations().size());
+                          assertThat(
+                              person.getFinalRecordComponents().get(1).getAnnotations(),
+                              hasAnnotationTypes(
+                                  inspector.getTypeSubject(
+                                      "records.RecordComponentAnnotationsTest$Annotation"),
+                                  inspector.getTypeSubject(
+                                      "records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly")));
+                          assertThat(
+                              person.getFinalRecordComponents().get(1).getAnnotations().get(0),
+                              hasElements(new Pair<>("value", "x")));
+                          assertThat(
+                              person.getFinalRecordComponents().get(1).getAnnotations().get(1),
+                              hasElements(new Pair<>("value", "z")));
+                        }));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addLibraryFiles(ToolHelper.getAndroidJar(35))
+        .addKeepMainRule(RecordWithAnnotations.class)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(
+            "records.RecordComponentAnnotationsTest$Person")
+        .addKeepClassAndMembersRules(
+            "records.RecordComponentAnnotationsTest$Annotation",
+            "records.RecordComponentAnnotationsTest$AnnotationFieldOnly",
+            "records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly")
+        .applyIf(keepAnnotations, TestShrinkerBuilder::addKeepRuntimeVisibleAnnotations)
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject person =
+                  inspector.clazz("records.RecordComponentAnnotationsTest$Person");
+              FieldSubject name = person.uniqueFieldWithOriginalName("name");
+              FieldSubject age = person.uniqueFieldWithOriginalName("age");
+              if (parameters.isCfRuntime()) {
+                assertEquals(2, person.getFinalRecordComponents().size());
+
+                assertEquals(
+                    name.getFinalName(), person.getFinalRecordComponents().get(0).getName());
+                assertTrue(
+                    person.getFinalRecordComponents().get(0).getType().is("java.lang.String"));
+                assertNull(person.getFinalRecordComponents().get(0).getSignature());
+                assertEquals(2, person.getFinalRecordComponents().get(0).getAnnotations().size());
+                assertThat(
+                    person.getFinalRecordComponents().get(0).getAnnotations(),
+                    hasAnnotationTypes(
+                        inspector.getTypeSubject(
+                            "records.RecordComponentAnnotationsTest$Annotation"),
+                        inspector.getTypeSubject(
+                            "records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly")));
+                assertThat(
+                    person.getFinalRecordComponents().get(0).getAnnotations().get(0),
+                    hasElements(new Pair<>("value", "a")));
+                assertThat(
+                    person.getFinalRecordComponents().get(0).getAnnotations().get(1),
+                    hasElements(new Pair<>("value", "c")));
+
+                assertEquals(
+                    age.getFinalName(), person.getFinalRecordComponents().get(1).getName());
+                assertTrue(person.getFinalRecordComponents().get(1).getType().is("int"));
+                assertNull(person.getFinalRecordComponents().get(1).getSignature());
+                assertEquals(2, person.getFinalRecordComponents().get(1).getAnnotations().size());
+                assertThat(
+                    person.getFinalRecordComponents().get(1).getAnnotations(),
+                    hasAnnotationTypes(
+                        inspector.getTypeSubject(
+                            "records.RecordComponentAnnotationsTest$Annotation"),
+                        inspector.getTypeSubject(
+                            "records.RecordComponentAnnotationsTest$AnnotationRecordComponentOnly")));
+                assertThat(
+                    person.getFinalRecordComponents().get(1).getAnnotations().get(0),
+                    hasElements(new Pair<>("value", "x")));
+                assertThat(
+                    person.getFinalRecordComponents().get(1).getAnnotations().get(1),
+                    hasElements(new Pair<>("value", "z")));
+              } else {
+                assertEquals(0, person.getFinalRecordComponents().size());
+              }
+            })
+        .run(parameters.getRuntime(), RecordWithAnnotations.class)
+        .applyIf(
+            // TODO(b/274888318): EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS still has component
+            //  annotations.
+            parameters.isCfRuntime(),
+            r ->
+                r.assertSuccessWithOutput(
+                    keepAnnotations
+                        ? JVM_EXPECTED_RESULT_R8
+                        : JVM_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS),
+            recordDesugaringIsOffOnDex,
+            r ->
+                r.assertSuccessWithOutput(
+                    keepAnnotations
+                        ? ART_EXPECTED_RESULT_R8
+                        : ART_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS),
+            r ->
+                r.assertSuccessWithOutput(
+                    runtimeWithRecordsSupport(parameters.getRuntime())
+                        ? EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT
+                        : EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT));
+  }
+
+  @Target({ElementType.FIELD, ElementType.RECORD_COMPONENT})
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface Annotation {
+
+    String value();
+  }
+
+  @Target(ElementType.FIELD)
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface AnnotationFieldOnly {
+
+    String value();
+  }
+
+  @Target(ElementType.RECORD_COMPONENT)
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface AnnotationRecordComponentOnly {
+
+    String value();
+  }
+
+  record Person(
+      @Annotation("a") @AnnotationFieldOnly("b") @AnnotationRecordComponentOnly("c") String name,
+      @Annotation("x") @AnnotationFieldOnly("y") @AnnotationRecordComponentOnly("z") int age) {}
+
+  public static class RecordWithAnnotations {
+
+    public static void main(String[] args) {
+      Person janeDoe = new Person("Jane Doe", 42);
+      System.out.println(janeDoe.name());
+      System.out.println(janeDoe.age());
+      try {
+        Class.class.getDeclaredMethod("isRecord");
+      } catch (NoSuchMethodException e) {
+        System.out.println("Class.isRecord not present");
+        return;
+      }
+      System.out.println(Person.class.isRecord());
+      if (Person.class.isRecord()) {
+        System.out.println(Person.class.getRecordComponents().length);
+        for (int i = 0; i < Person.class.getRecordComponents().length; i++) {
+          RecordComponent c = Person.class.getRecordComponents()[i];
+          System.out.println(c.getName());
+          System.out.println(c.getType().getName());
+          System.out.println(c.getGenericSignature() == null);
+          System.out.println(c.getAnnotations().length);
+          // Collect and sort the annotations, as the order is not deterministic on Art (tested
+          // on Art 14 Beta 3).
+          List<String> annotations = new ArrayList<>();
+          for (int j = 0; j < c.getAnnotations().length; j++) {
+            annotations.add(c.getAnnotations()[j].toString());
+          }
+          annotations.sort(Comparator.naturalOrder());
+          for (int j = 0; j < annotations.size(); j++) {
+            System.out.println(annotations.get(j));
+          }
+        }
+        System.out.println(Person.class.getDeclaredFields().length);
+        List<Field> fields = new ArrayList<>();
+        for (int i = 0; i < Person.class.getDeclaredFields().length; i++) {
+          fields.add(Person.class.getDeclaredFields()[i]);
+        }
+        fields.sort(
+            new Comparator<Field>() {
+              @Override
+              public int compare(Field o1, Field o2) {
+                return o1.getName().compareTo(o2.getName());
+              }
+            });
+        for (int i = 0; i < fields.size(); i++) {
+          Field f = fields.get(i);
+          System.out.println(f.getDeclaredAnnotations().length);
+          List<String> annotations = new ArrayList<>();
+          for (int j = 0; j < f.getDeclaredAnnotations().length; j++) {
+            annotations.add(f.getAnnotations()[j].toString());
+          }
+          annotations.sort(Comparator.naturalOrder());
+          for (int j = 0; j < annotations.size(); j++) {
+            System.out.println(annotations.get(j));
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java b/src/test/examplesJava17/records/RecordComponentSignatureTest.java
similarity index 74%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
rename to src/test/examplesJava17/records/RecordComponentSignatureTest.java
index 27e7e0b..d2d530b 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
+++ b/src/test/examplesJava17/records/RecordComponentSignatureTest.java
@@ -1,7 +1,7 @@
 // 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.desugar.records;
+package records;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -13,10 +13,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.lang.reflect.RecordComponent;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,15 +29,10 @@
 @RunWith(Parameterized.class)
 public class RecordComponentSignatureTest extends TestBase {
 
-  private static final String RECORD_NAME = "RecordWithSignature";
-  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",
           "true",
           "2",
           "name",
@@ -46,12 +43,11 @@
           "java.lang.Object",
           "TA;",
           "0");
-  private static final String EXPECTED_RESULT_R8 =
-      StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "true", "0");
+  private static final String EXPECTED_RESULT_R8 = StringUtils.lines("Jane Doe", "42", "true", "0");
   private static final String EXPECTED_RESULT_DESUGARED_NO_NATIVE_RECORDS_SUPPORT =
-      StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "Class.isRecord not present");
+      StringUtils.lines("Jane Doe", "42", "Class.isRecord not present");
   private static final String EXPECTED_RESULT_DESUGARED_NATIVE_RECORD_SUPPORT =
-      StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "false");
+      StringUtils.lines("Jane Doe", "42", "false");
 
   @Parameter(0)
   public TestParameters parameters;
@@ -75,8 +71,8 @@
     parameters.assumeJvmTestParameters();
     assumeTrue(keepSignatures);
     testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), RecordWithSignature.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
@@ -85,8 +81,8 @@
     parameters.assumeDexRuntime();
     assumeTrue(keepSignatures);
     testForDesugaring(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), RecordWithSignature.class)
         .applyIf(
             parameters.isCfRuntime(),
             r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
@@ -98,7 +94,7 @@
                     .inspect(
                         inspector -> {
                           ClassSubject person =
-                              inspector.clazz("records.RecordWithSignature$Person");
+                              inspector.clazz("records.RecordComponentSignatureTest$Person");
                           if (parameters.isCfRuntime()) {
                             assertEquals(2, person.getFinalRecordComponents().size());
 
@@ -138,22 +134,20 @@
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        // TODO(b/231930852): Change to android.jar for Android U when that contains
-        // java.lang.Record.
-        .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-        .addKeepMainRule(MAIN_TYPE)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addLibraryFiles(ToolHelper.getAndroidJar(35))
+        .addKeepMainRule(RecordWithSignature.class)
         .applyIf(keepSignatures, TestShrinkerBuilder::addKeepAttributeSignature)
         .setMinApi(parameters)
         .compile()
         .inspect(
             inspector -> {
-              ClassSubject person = inspector.clazz("records.RecordWithSignature$Person");
+              ClassSubject person = inspector.clazz("records.RecordComponentSignatureTest$Person");
               FieldSubject age = person.uniqueFieldWithOriginalName("age");
               assertThat(age, isAbsent());
               assertEquals(0, person.getFinalRecordComponents().size());
             })
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordWithSignature.class)
         .applyIf(
             runtimeWithRecordsSupport(parameters.getRuntime()),
             r ->
@@ -163,4 +157,32 @@
                         : EXPECTED_RESULT_R8),
             r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_NO_NATIVE_RECORDS_SUPPORT));
   }
+
+  record Person<N extends CharSequence, A>(N name, A age) {}
+
+  public class RecordWithSignature {
+
+    public static void main(String[] args) {
+      Person<String, Integer> janeDoe = new Person<>("Jane Doe", 42);
+      System.out.println(janeDoe.name());
+      System.out.println(janeDoe.age());
+      try {
+        Class.class.getDeclaredMethod("isRecord");
+      } catch (NoSuchMethodException e) {
+        System.out.println("Class.isRecord not present");
+        return;
+      }
+      System.out.println(Person.class.isRecord());
+      if (Person.class.isRecord()) {
+        System.out.println(Person.class.getRecordComponents().length);
+        for (int i = 0; i < Person.class.getRecordComponents().length; i++) {
+          RecordComponent c = Person.class.getRecordComponents()[i];
+          System.out.println(c.getName());
+          System.out.println(c.getType().getName());
+          System.out.println(c.getGenericSignature());
+          System.out.println(c.getAnnotations().length);
+        }
+      }
+    }
+  }
 }
diff --git a/src/test/examplesJava17/records/RecordInterface.java b/src/test/examplesJava17/records/RecordInterface.java
deleted file mode 100644
index 8fc1e56..0000000
--- a/src/test/examplesJava17/records/RecordInterface.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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 records;
-
-public class RecordInterface {
-
-  interface Human {
-    default void printHuman() {
-      System.out.println("Human");
-    }
-  }
-
-  record Person(String name, int age) implements Human {}
-
-  public static void main(String[] args) {
-    Person janeDoe = new Person("Jane Doe", 42);
-    janeDoe.printHuman();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java b/src/test/examplesJava17/records/RecordInterfaceTest.java
similarity index 74%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
rename to src/test/examplesJava17/records/RecordInterfaceTest.java
index 169ef16..d5144b0 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
+++ b/src/test/examplesJava17/records/RecordInterfaceTest.java
@@ -2,26 +2,31 @@
 // 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;
+package records;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.DesugarGraphTestConsumer;
 import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.desugar.graph.DesugarGraphTestConsumer;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,9 +36,6 @@
 @RunWith(Parameterized.class)
 public class RecordInterfaceTest extends TestBase {
 
-  private static final String RECORD_NAME = "RecordInterface";
-  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("Human");
 
   @Parameter(0)
@@ -54,17 +56,17 @@
   public void testReference() throws Exception {
     assumeTrue(isCfRuntimeWithNativeRecordSupport());
     testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), RecordInterface.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
+        .addInnerClassesAndStrippedOuter(getClass())
         .setMinApi(parameters)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordInterface.class)
         .applyIf(
             isRecordsFullyDesugaredForD8(parameters)
                 || runtimeWithRecordsSupport(parameters.getRuntime()),
@@ -91,7 +93,7 @@
         .setIncludeClassesChecksum(true)
         .compile()
         .assertNoMessages()
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordInterface.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
     assertNoEdgeToRecord(consumer);
   }
@@ -117,7 +119,7 @@
         .disableDesugaring()
         .compile()
         .assertNoMessages()
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordInterface.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
     assertNoEdgeToRecord(consumer);
   }
@@ -128,12 +130,16 @@
     DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
     Path intermediate =
         testForD8(Backend.DEX)
+            .addStrippedOuter(getClass(), fake)
             .apply(
                 b -> {
-                  // We avoid unknown origin here since they are not allowed when using a Graph
-                  // consumer.
-                  for (byte[] programDatum : PROGRAM_DATA) {
-                    b.getBuilder().addClassProgramData(programDatum, fake);
+                  try {
+                    for (Path file :
+                        ToolHelper.getClassFilesForInnerClasses(ImmutableList.of(getClass()))) {
+                      b.getBuilder().addClassProgramData(Files.readAllBytes(file), fake);
+                    }
+                  } catch (IOException e) {
+                    throw new RuntimeException(e);
                   }
                 })
             .setMinApi(parameters)
@@ -149,7 +155,7 @@
   }
 
   private void assertNoEdgeToRecord(DesugarGraphTestConsumer consumer) {
-    assertEquals(0, consumer.totalEdgeCount());
+    Assert.assertEquals(0, consumer.totalEdgeCount());
   }
 
   @Test
@@ -158,18 +164,37 @@
     assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA)
+            .addInnerClassesAndStrippedOuter(getClass())
             .setMinApi(parameters)
-            .addKeepMainRule(MAIN_TYPE);
+            .addKeepMainRule(RecordInterface.class);
     if (parameters.isCfRuntime()) {
       builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
           .compile()
           .inspect(RecordTestUtils::assertRecordsAreRecords)
-          .run(parameters.getRuntime(), MAIN_TYPE)
+          .run(parameters.getRuntime(), RecordInterface.class)
           .assertSuccessWithOutput(EXPECTED_RESULT);
       return;
     }
-    builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+    builder
+        .run(parameters.getRuntime(), RecordInterface.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  interface Human {
+
+    default void printHuman() {
+      System.out.println("Human");
+    }
+  }
+
+  record Person(String name, int age) implements Human {}
+
+  public class RecordInterface {
+
+    public static void main(String[] args) {
+      Person janeDoe = new Person("Jane Doe", 42);
+      janeDoe.printHuman();
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java b/src/test/examplesJava17/records/RecordInvokeCustomSplitDesugaringTest.java
similarity index 67%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
rename to src/test/examplesJava17/records/RecordInvokeCustomSplitDesugaringTest.java
index b782568..d797244 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
+++ b/src/test/examplesJava17/records/RecordInvokeCustomSplitDesugaringTest.java
@@ -2,7 +2,7 @@
 // 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;
+package records;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
@@ -25,13 +25,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import records.RecordInvokeCustom.Empty;
+import records.RecordInvokeCustom.Person;
 
 @RunWith(Parameterized.class)
 public class RecordInvokeCustomSplitDesugaringTest 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(
           "%s[]",
@@ -64,7 +63,7 @@
   public void testD8() throws Exception {
     Path desugared =
         testForD8(Backend.CF)
-            .addProgramClassFileData(PROGRAM_DATA)
+            .addInnerClassesAndStrippedOuter(getClass())
             .setMinApi(parameters)
             .compile()
             .writeToZip();
@@ -72,7 +71,7 @@
         .addProgramFiles(desugared)
         .setMinApi(parameters)
         .compile()
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordInvokeCustom.class)
         .assertSuccessWithOutput(EXPECTED_RESULT_D8);
   }
 
@@ -81,7 +80,7 @@
     parameters.assumeR8TestParameters();
     Path desugared =
         testForD8(Backend.CF)
-            .addProgramClassFileData(PROGRAM_DATA)
+            .addInnerClassesAndStrippedOuter(getClass())
             .setMinApi(parameters)
             .compile()
             .writeToZip();
@@ -94,7 +93,7 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(desugared)
         .setMinApi(parameters)
-        .addKeepMainRule(MAIN_TYPE)
+        .addKeepMainRule(RecordInvokeCustom.class)
         .allowDiagnosticMessages()
         .compileWithExpectedDiagnostics(
             // Class com.android.tools.r8.RecordTag in desugared input is seen as java.lang.Record
@@ -119,10 +118,12 @@
             })
         .inspect(
             i -> {
-              minifiedNames[0] = extractSimpleFinalName(i, "records.RecordInvokeCustom$Empty");
-              minifiedNames[1] = extractSimpleFinalName(i, "records.RecordInvokeCustom$Person");
+              minifiedNames[0] =
+                  extractSimpleFinalName(i, "records.RecordInvokeCustomSplitDesugaringTest$Empty");
+              minifiedNames[1] =
+                  extractSimpleFinalName(i, "records.RecordInvokeCustomSplitDesugaringTest$Person");
             })
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordInvokeCustom.class)
         .assertSuccessWithOutput(
             String.format(EXPECTED_RESULT, minifiedNames[0], minifiedNames[1]));
   }
@@ -131,4 +132,47 @@
     String finalName = i.clazz(name).getFinalName();
     return finalName.split("\\.")[1];
   }
+
+  record Empty() {}
+
+  record Person(String name, int age) {}
+
+  public class RecordInvokeCustom {
+
+    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/examplesJava17/records/RecordInvokeCustomTest.java b/src/test/examplesJava17/records/RecordInvokeCustomTest.java
new file mode 100644
index 0000000..ec3956a
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordInvokeCustomTest.java
@@ -0,0 +1,140 @@
+// 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 com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.desugar.LibraryFilesHelper;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import records.RecordInvokeCustom.Empty;
+import records.RecordInvokeCustom.Person;
+
+@RunWith(Parameterized.class)
+public class RecordInvokeCustomTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "%s[]",
+          "true",
+          "true",
+          "true",
+          "true",
+          "true",
+          "false",
+          "true",
+          "true",
+          "false",
+          "false",
+          "%s[%s=Jane Doe, %s=42]");
+  private static final String EXPECTED_RESULT_D8 =
+      String.format(EXPECTED_RESULT, "Empty", "Person", "name", "age");
+  private static final String EXPECTED_RESULT_R8 =
+      String.format(EXPECTED_RESULT, "a", "b", "a", "b");
+
+  private final TestParameters parameters;
+
+  public RecordInvokeCustomTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+        .withDexRuntimes()
+        .withAllApiLevelsAlsoForCf()
+        .build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), RecordInvokeCustom.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT_D8);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), RecordInvokeCustom.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT_D8);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addInnerClassesAndStrippedOuter(getClass())
+            .setMinApi(parameters)
+            .addKeepMainRule(RecordInvokeCustom.class);
+    if (parameters.isCfRuntime()) {
+      builder
+          .addLibraryFiles(LibraryFilesHelper.getJdk15LibraryFiles(temp))
+          .compile()
+          .inspect(RecordTestUtils::assertRecordsAreRecords)
+          .run(parameters.getRuntime(), RecordInvokeCustom.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT_R8);
+      return;
+    }
+    builder
+        .run(parameters.getRuntime(), RecordInvokeCustom.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT_R8);
+  }
+
+  record Empty() {}
+
+  record Person(String name, int age) {}
+
+  public class RecordInvokeCustom {
+
+    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/examplesJava17/records/RecordShrinkField.java b/src/test/examplesJava17/records/RecordKeepRules.java
similarity index 73%
rename from src/test/examplesJava17/records/RecordShrinkField.java
rename to src/test/examplesJava17/records/RecordKeepRules.java
index 2a21904..a62132f 100644
--- a/src/test/examplesJava17/records/RecordShrinkField.java
+++ b/src/test/examplesJava17/records/RecordKeepRules.java
@@ -4,7 +4,9 @@
 
 package records;
 
-public class RecordShrinkField {
+// The code needs to be completely outside the test for the test to work.
+// If these classes are inner classes, age is not correctly simplified to a 42 constant field.
+public class RecordKeepRules {
 
   record Person(int unused, String name, int age) {
     Person(String name, int age) {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java b/src/test/examplesJava17/records/RecordKeepRulesTest.java
similarity index 76%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
rename to src/test/examplesJava17/records/RecordKeepRulesTest.java
index 68c85df..cf864fe 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
+++ b/src/test/examplesJava17/records/RecordKeepRulesTest.java
@@ -2,8 +2,9 @@
 // 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;
+package records;
 
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -17,30 +18,26 @@
 @RunWith(Parameterized.class)
 public class RecordKeepRulesTest extends TestBase {
 
-  private static final String RECORD_NAME = "RecordShrinkField";
-  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
-  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
-
   private static final String KEEP_RULE_CLASS_NAME =
-      "-keep,allowshrinking,allowoptimization class records.RecordShrinkField$Person";
+      "-keep,allowshrinking,allowoptimization class records.RecordKeepRules$Person";
   private static final String KEEP_RULE_FIELD_NAMES =
-      "-keepclassmembers,allowshrinking,allowoptimization class records.RecordShrinkField$Person {"
+      "-keepclassmembers,allowshrinking,allowoptimization class records.RecordKeepRules$Person {"
           + " <fields>; }";
   private static final String KEEP_RULE_FIELDS_NO_NAMES =
-      "-keepclassmembers,allowobfuscation class records.RecordShrinkField$Person { <fields>; }";
+      "-keepclassmembers,allowobfuscation class records.RecordKeepRules$Person { <fields>; }";
   private static final String KEEP_RULE_ALL =
-      "-keep class records.RecordShrinkField$Person { <fields>; }";
+      "-keep class records.RecordKeepRules$Person { <fields>; }";
 
   private static final String EXPECTED_RESULT_R8_WITH_CLASS_NAME =
-      StringUtils.lines("RecordShrinkField$Person[a=Jane Doe]", "RecordShrinkField$Person[a=Bob]");
+      StringUtils.lines("RecordKeepRules$Person[a=Jane Doe]", "RecordKeepRules$Person[a=Bob]");
   private static final String EXPECTED_RESULT_R8_WITH_FIELD_NAMES =
       StringUtils.lines("a[name=Jane Doe]", "a[name=Bob]");
   private static final String EXPECTED_RESULT_R8_WITH_FIELD_NO_NAMES =
       StringUtils.lines("a[a=-1, b=Jane Doe, c=42]", "a[a=-1, b=Bob, c=42]");
   private static final String EXPECTED_RESULT_R8_WITH_ALL =
       StringUtils.lines(
-          "RecordShrinkField$Person[unused=-1, name=Jane Doe, age=42]",
-          "RecordShrinkField$Person[unused=-1, name=Bob, age=42]");
+          "RecordKeepRules$Person[unused=-1, name=Jane Doe, age=42]",
+          "RecordKeepRules$Person[unused=-1, name=Bob, age=42]");
 
   private final TestParameters parameters;
   private final boolean proguardCompatibility;
@@ -82,27 +79,27 @@
 
   private void testR8FieldNames(String keepRules, String expectedOutput) throws Exception {
     testForR8Compat(parameters.getBackend(), proguardCompatibility)
-        .addProgramClassFileData(PROGRAM_DATA)
+        .addProgramClassesAndInnerClasses(RecordKeepRules.class)
         .setMinApi(parameters)
-        .addKeepMainRule(MAIN_TYPE)
+        .addKeepMainRule(RecordKeepRules.class)
         .addKeepRules(keepRules)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordKeepRules.class)
         .assertSuccessWithOutput(expectedOutput);
   }
 
   private void testR8CfThenDexFieldNames(String keepRules, String expectedOutput) throws Exception {
     Path desugared =
         testForR8Compat(Backend.CF, proguardCompatibility)
-            .addProgramClassFileData(PROGRAM_DATA)
-            .addKeepMainRule(MAIN_TYPE)
+            .addProgramClassesAndInnerClasses(RecordKeepRules.class)
+            .addKeepMainRule(RecordKeepRules.class)
             .addKeepRules(keepRules)
-            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+            .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
             .compile()
             .writeToZip();
     testForD8(parameters.getBackend())
         .addProgramFiles(desugared)
         .setMinApi(parameters)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordKeepRules.class)
         .assertSuccessWithOutput(expectedOutput);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java b/src/test/examplesJava17/records/RecordLibMergeTest.java
similarity index 70%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java
rename to src/test/examplesJava17/records/RecordLibMergeTest.java
index ca7a7b5..fd0e655 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java
+++ b/src/test/examplesJava17/records/RecordLibMergeTest.java
@@ -2,8 +2,9 @@
 // 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;
+package records;
 
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -18,11 +19,6 @@
 @RunWith(Parameterized.class)
 public class RecordLibMergeTest extends TestBase {
 
-  private static final String RECORD_LIB = "RecordLib";
-  private static final String RECORD_MAIN = "RecordMain";
-  private static final byte[][] PROGRAM_DATA_LIB = RecordTestUtils.getProgramData(RECORD_LIB);
-  private static final byte[][] PROGRAM_DATA_MAIN = RecordTestUtils.getProgramData(RECORD_MAIN);
-  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_MAIN);
   private static final String EXPECTED_RESULT = StringUtils.lines("true", "true");
 
   private final TestParameters parameters;
@@ -45,30 +41,30 @@
     parameters.assumeR8TestParameters();
     Path lib =
         testForR8(Backend.CF)
-            .addProgramClassFileData(PROGRAM_DATA_LIB)
+            .addProgramClassesAndInnerClasses(RecordLib.class)
             .addKeepRules(
                 "-keep class records.RecordLib { public static java.lang.Object getRecord(); }")
             .addKeepRules("-keep class records.RecordLib$LibRecord")
-            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+            .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
             .compile()
             .writeToZip();
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
             .addProgramFiles(lib)
-            .addProgramClassFileData(PROGRAM_DATA_MAIN)
+            .addProgramClassesAndInnerClasses(RecordMain.class)
             .setMinApi(parameters)
-            .addKeepMainRule(MAIN_TYPE)
+            .addKeepMainRule(RecordMain.class)
             .addKeepRules("-keep class records.RecordLib$LibRecord")
             .addKeepRules("-keep class records.RecordMain$MainRecord");
     if (parameters.isCfRuntime()) {
       builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
           .compile()
           .inspect(RecordTestUtils::assertRecordsAreRecords)
-          .run(parameters.getRuntime(), MAIN_TYPE)
+          .run(parameters.getRuntime(), RecordMain.class)
           .assertSuccessWithOutput(EXPECTED_RESULT);
       return;
     }
-    builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+    builder.run(parameters.getRuntime(), RecordMain.class).assertSuccessWithOutput(EXPECTED_RESULT);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/examplesJava17/records/RecordMergeTest.java
similarity index 67%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
rename to src/test/examplesJava17/records/RecordMergeTest.java
index d05a57a..4153e38 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/examplesJava17/records/RecordMergeTest.java
@@ -2,7 +2,7 @@
 // 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;
+package records;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
@@ -14,13 +14,13 @@
 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.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
@@ -32,29 +32,12 @@
 @RunWith(Parameterized.class)
 public class RecordMergeTest extends TestBase {
 
-  private static final String RECORD_NAME_1 = "RecordWithMembers";
-  private static final byte[][] PROGRAM_DATA_1 = RecordTestUtils.getProgramData(RECORD_NAME_1);
-  private static final String MAIN_TYPE_1 = RecordTestUtils.getMainType(RECORD_NAME_1);
   private static final String EXPECTED_RESULT_1 =
-      StringUtils.lines(
-          "BobX", "43", "BobX", "43", "FelixX", "-1", "FelixX", "-1", "print", "Bob43", "extra");
+      StringUtils.lines("BobX", "43", "FelixX", "-1", "print", "Bob43", "extra");
 
-  private static final String RECORD_NAME_2 = "SimpleRecord";
-  private static final byte[][] PROGRAM_DATA_2 = RecordTestUtils.getProgramData(RECORD_NAME_2);
-  private static final String MAIN_TYPE_2 = RecordTestUtils.getMainType(RECORD_NAME_2);
   private static final String EXPECTED_RESULT_2 =
       StringUtils.lines(
-          "Jane Doe",
-          "42",
-          "Jane Doe",
-          "42",
-          "true",
-          "true",
-          "true",
-          "false",
-          "false",
-          "false",
-          "false");
+          "Jane Doe", "42", "true", "true", "true", "false", "false", "false", "false");
 
   private final TestParameters parameters;
 
@@ -71,7 +54,9 @@
   public void testNoGlobalSyntheticsConsumer() throws Exception {
     D8TestBuilder builder =
         testForD8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA_1)
+            .addStrippedOuter(getClass())
+            .addProgramClassesAndInnerClasses(RecordWithMembers.class)
+            .addClasspathClassesAndInnerClasses(SimpleRecord.class)
             .setMinApi(parameters)
             .setIntermediate(true);
     if (isRecordsFullyDesugaredForD8(parameters)) {
@@ -104,7 +89,9 @@
     GlobalSyntheticsTestingConsumer globals1 = new GlobalSyntheticsTestingConsumer();
     Path output1 =
         testForD8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA_1)
+            .addStrippedOuter(getClass())
+            .addProgramClassesAndInnerClasses(RecordWithMembers.class)
+            .addClasspathClassesAndInnerClasses(SimpleRecord.class)
             .setMinApi(parameters)
             .setIntermediate(true)
             .applyIf(
@@ -118,7 +105,8 @@
     GlobalSyntheticsTestingConsumer globals2 = new GlobalSyntheticsTestingConsumer();
     Path output2 =
         testForD8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA_2)
+            .addProgramClassesAndInnerClasses(SimpleRecord.class)
+            .addClasspathClassesAndInnerClasses(getClass())
             .setMinApi(parameters)
             .setIntermediate(true)
             .applyIf(
@@ -145,14 +133,14 @@
             .inspect(this::assertHasRecordTag);
 
     result
-        .run(parameters.getRuntime(), MAIN_TYPE_1)
+        .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(), MAIN_TYPE_2)
+        .run(parameters.getRuntime(), SimpleRecord.class)
         .applyIf(
             isRecordsFullyDesugaredForD8(parameters)
                 || runtimeWithRecordsSupport(parameters.getRuntime()),
@@ -165,7 +153,9 @@
     GlobalSyntheticsTestingConsumer globals1 = new GlobalSyntheticsTestingConsumer();
     Path output1 =
         testForD8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA_1)
+            .addProgramClassesAndInnerClasses(RecordWithMembers.class)
+            .addClasspathClasses(getClass())
+            .addClasspathClassesAndInnerClasses(SimpleRecord.class)
             .setMinApi(parameters)
             .setIntermediate(true)
             .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1))
@@ -174,21 +164,23 @@
 
     D8TestCompileResult result =
         testForD8(parameters.getBackend())
+            .addStrippedOuter(getClass())
             .addProgramFiles(output1)
             .apply(
                 b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1.getProviders()))
-            .addProgramClassFileData(PROGRAM_DATA_2)
+            .addProgramClassesAndInnerClasses(SimpleRecord.class)
+            .addClasspathClassesAndInnerClasses(getClass())
             .setMinApi(parameters)
             .compile();
     result
-        .run(parameters.getRuntime(), MAIN_TYPE_1)
+        .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(), MAIN_TYPE_2)
+        .run(parameters.getRuntime(), SimpleRecord.class)
         .applyIf(
             isRecordsFullyDesugaredForD8(parameters)
                 || runtimeWithRecordsSupport(parameters.getRuntime()),
@@ -200,7 +192,9 @@
   public void testMergeNonIntermediates() throws Exception {
     Path output1 =
         testForD8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA_1)
+            .addStrippedOuter(getClass())
+            .addProgramClassesAndInnerClasses(RecordWithMembers.class)
+            .addClasspathClassesAndInnerClasses(SimpleRecord.class)
             .setMinApi(parameters)
             .compile()
             .inspect(this::assertHasRecordTag)
@@ -208,7 +202,8 @@
 
     Path output2 =
         testForD8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA_2)
+            .addProgramClassesAndInnerClasses(SimpleRecord.class)
+            .addClasspathClassesAndInnerClasses(getClass())
             .setMinApi(parameters)
             .compile()
             .inspect(this::assertHasRecordTag)
@@ -221,14 +216,14 @@
               .setMinApi(parameters)
               .compile();
       result
-          .run(parameters.getRuntime(), MAIN_TYPE_1)
+          .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(), MAIN_TYPE_2)
+          .run(parameters.getRuntime(), SimpleRecord.class)
           .applyIf(
               isRecordsFullyDesugaredForD8(parameters)
                   || runtimeWithRecordsSupport(parameters.getRuntime()),
@@ -259,4 +254,89 @@
     // 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));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/examplesJava17/records/RecordProfileRewritingTest.java
similarity index 86%
rename from src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
rename to src/test/examplesJava17/records/RecordProfileRewritingTest.java
index 602cdfc..13c5b5a 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
+++ b/src/test/examplesJava17/records/RecordProfileRewritingTest.java
@@ -2,7 +2,7 @@
 // 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.profile.art.completeness;
+package records;
 
 import static com.android.tools.r8.ir.desugar.records.RecordFullInstructionDesugaring.EQUALS_RECORD_METHOD_NAME;
 import static com.android.tools.r8.ir.desugar.records.RecordFullInstructionDesugaring.GET_FIELDS_AS_OBJECTS_METHOD_NAME;
@@ -16,11 +16,11 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.desugar.records.RecordTestUtils;
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
 import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
 import com.android.tools.r8.references.ClassReference;
@@ -43,26 +43,12 @@
 @RunWith(Parameterized.class)
 public class RecordProfileRewritingTest extends TestBase {
 
-  private static final String RECORD_NAME = "SimpleRecord";
-  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
   private static final String EXPECTED_RESULT =
       StringUtils.lines(
-          "Jane Doe",
-          "42",
-          "Jane Doe",
-          "42",
-          "true",
-          "true",
-          "true",
-          "false",
-          "false",
-          "false",
-          "false");
+          "Jane Doe", "42", "true", "true", "true", "false", "false", "false", "false");
 
-  private static final ClassReference MAIN_REFERENCE =
-      Reference.classFromTypeName(RecordTestUtils.getMainType(RECORD_NAME));
-  private static final ClassReference PERSON_REFERENCE =
-      Reference.classFromTypeName(MAIN_REFERENCE.getTypeName() + "$Person");
+  private static final ClassReference MAIN_REFERENCE = Reference.classFromClass(SimpleRecord.class);
+  private static final ClassReference PERSON_REFERENCE = Reference.classFromClass(Person.class);
   private static final ClassReference RECORD_REFERENCE =
       Reference.classFromTypeName("java.lang.Record");
   private static final ClassReference OBJECT_REFERENCE = Reference.classFromClass(Object.class);
@@ -81,7 +67,7 @@
     parameters.assumeJvmTestParameters();
     assumeTrue(runtimeWithRecordsSupport(parameters.getRuntime()));
     testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
+        .addInnerClassesAndStrippedOuter(getClass())
         .run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
@@ -90,7 +76,7 @@
   public void testD8() throws Exception {
     D8TestCompileResult compileResult =
         testForD8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA)
+            .addInnerClassesAndStrippedOuter(getClass())
             .addArtProfileForRewriting(getArtProfile())
             .setMinApi(parameters)
             .compile();
@@ -114,7 +100,7 @@
     assumeTrue(runtimeWithRecordsSupport(parameters.getRuntime()) || parameters.isDexRuntime());
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA)
+            .addInnerClassesAndStrippedOuter(getClass())
             .addKeepMainRule(MAIN_REFERENCE.getTypeName())
             .addKeepRules(
                 "-neverpropagatevalue class " + PERSON_REFERENCE.getTypeName() + " { <fields>; }")
@@ -122,8 +108,7 @@
             .addOptionsModification(InlinerOptions::disableInlining)
             .applyIf(
                 parameters.isCfRuntime(),
-                testBuilder ->
-                    testBuilder.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)))
+                testBuilder -> testBuilder.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
             .enableProguardTestOptions()
             .noHorizontalClassMergingOfSynthetics()
             .setMinApi(parameters)
@@ -218,7 +203,7 @@
                 : recordTagClassSubject.asTypeSubject(),
         personRecordClassSubject.getSuperType());
     assertEquals(
-        recordDesugaringIsOff ? 6 : (canMergeRecordTag && !partialDesugaring) ? 11 : 10,
+        recordDesugaringIsOff ? 6 : (canMergeRecordTag && !partialDesugaring) ? 9 : 8,
         personRecordClassSubject.allMethods().size());
 
     MethodSubject personDefaultInstanceInitializerSubject = personRecordClassSubject.init();
@@ -236,24 +221,10 @@
     MethodSubject nameMethodSubject = personRecordClassSubject.uniqueMethodWithOriginalName("name");
     assertThat(nameMethodSubject, isPresent());
 
-    MethodSubject nameNestAccessorMethodSubject =
-        personRecordClassSubject.uniqueMethodWithOriginalName(
-            SyntheticItemsTestUtils.syntheticNestInstanceFieldGetter(
-                    Reference.field(PERSON_REFERENCE, "name", STRING_REFERENCE))
-                .getMethodName());
-    assertThat(nameNestAccessorMethodSubject, isAbsentIf(canUseNestBasedAccesses));
-
     // Age getters.
     MethodSubject ageMethodSubject = personRecordClassSubject.uniqueMethodWithOriginalName("age");
     assertThat(ageMethodSubject, isPresent());
 
-    MethodSubject ageNestAccessorMethodSubject =
-        personRecordClassSubject.uniqueMethodWithOriginalName(
-            SyntheticItemsTestUtils.syntheticNestInstanceFieldGetter(
-                    Reference.field(PERSON_REFERENCE, "age", Reference.INT))
-                .getMethodName());
-    assertThat(ageNestAccessorMethodSubject, isAbsentIf(canUseNestBasedAccesses));
-
     // boolean equals(Object)
     MethodSubject getFieldsAsObjectsMethodSubject =
         personRecordClassSubject.uniqueMethodWithOriginalName(GET_FIELDS_AS_OBJECTS_METHOD_NAME);
@@ -272,7 +243,7 @@
 
     // int hashCode()
     ClassSubject hashCodeHelperClassSubject =
-        inspector.clazz(SyntheticItemsTestUtils.syntheticRecordHelperClass(PERSON_REFERENCE, 1));
+        inspector.clazz(SyntheticItemsTestUtils.syntheticRecordHelperClass(PERSON_REFERENCE, 0));
     assertThat(hashCodeHelperClassSubject, isAbsentIf(recordDesugaringIsOff));
 
     MethodSubject hashCodeHelperMethodSubject = hashCodeHelperClassSubject.uniqueMethod();
@@ -290,7 +261,7 @@
 
     // String toString()
     ClassSubject toStringHelperClassSubject =
-        inspector.clazz(SyntheticItemsTestUtils.syntheticRecordHelperClass(PERSON_REFERENCE, 0));
+        inspector.clazz(SyntheticItemsTestUtils.syntheticRecordHelperClass(PERSON_REFERENCE, 1));
     assertThat(toStringHelperClassSubject, isAbsentIf(recordDesugaringIsOff));
 
     MethodSubject toStringHelperMethodSubject = toStringHelperClassSubject.uniqueMethod();
@@ -317,11 +288,6 @@
             hashCodeMethodSubject,
             toStringMethodSubject)
         .applyIf(
-            !canUseNestBasedAccesses,
-            i ->
-                i.assertContainsMethodRules(
-                    nameNestAccessorMethodSubject, ageNestAccessorMethodSubject))
-        .applyIf(
             canMergeRecordTag && !partialDesugaring,
             i -> i.assertContainsMethodRule(personDefaultInstanceInitializerSubject))
         .applyIf(
@@ -340,4 +306,32 @@
                         toStringHelperMethodSubject))
         .assertContainsNoOtherRules();
   }
+
+  record Person(String name, int age) {}
+
+  public class SimpleRecord {
+
+    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));
+    }
+  }
 }
diff --git a/src/test/examplesJava17/records/RecordReflection.java b/src/test/examplesJava17/records/RecordReflection.java
deleted file mode 100644
index 1b94a12..0000000
--- a/src/test/examplesJava17/records/RecordReflection.java
+++ /dev/null
@@ -1,29 +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 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/examplesJava17/records/RecordReflectionTest.java b/src/test/examplesJava17/records/RecordReflectionTest.java
new file mode 100644
index 0000000..bab9fb7
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordReflectionTest.java
@@ -0,0 +1,89 @@
+// 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 com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+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 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 TestParametersCollection data() {
+    return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK17).build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), RecordReflection.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8Cf() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .setMinApi(parameters)
+        .addKeepMainRule(RecordReflection.class)
+        .addKeepAllAttributes()
+        .addKeepRules("-keep class * extends java.lang.Record { private final <fields>; }")
+        .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+        .compile()
+        .inspect(RecordTestUtils::assertRecordsAreRecords)
+        .enableJVMPreview()
+        .run(parameters.getRuntime(), RecordReflection.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  record Empty() {}
+
+  record Person(String name, int age) {}
+
+  record PersonGeneric<S extends CharSequence>(S name, int age) {}
+
+  public class RecordReflection {
+
+    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/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java b/src/test/examplesJava17/records/RecordShrinkFieldTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
rename to src/test/examplesJava17/records/RecordShrinkFieldTest.java
index e8587ca..232f02f 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
+++ b/src/test/examplesJava17/records/RecordShrinkFieldTest.java
@@ -2,10 +2,11 @@
 // 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;
+package records;
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -22,17 +23,13 @@
 @RunWith(Parameterized.class)
 public class RecordShrinkFieldTest extends TestBase {
 
-  private static final String RECORD_NAME = "RecordShrinkField";
-  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_D8 =
       StringUtils.lines(
           "Person[unused=-1, name=Jane Doe, age=42]", "Person[unused=-1, name=Bob, age=42]");
   private static final String EXPECTED_RESULT_R8 = StringUtils.lines("a[a=Jane Doe]", "a[a=Bob]");
   private static final String EXPECTED_RESULT_R8_NO_MINIFICATION =
       StringUtils.lines(
-          "RecordShrinkField$Person[name=Jane Doe]", "RecordShrinkField$Person[name=Bob]");
+          "RecordShrinkFieldTest$Person[name=Jane Doe]", "RecordShrinkFieldTest$Person[name=Bob]");
 
   private final TestParameters parameters;
   private final boolean minifying;
@@ -53,10 +50,10 @@
   public void testD8() throws Exception {
     Assume.assumeTrue("Only valid in R8", minifying);
     testForD8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
+        .addInnerClassesAndStrippedOuter(getClass())
         .setMinApi(parameters)
         .compile()
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordShrinkField.class)
         .assertSuccessWithOutput(EXPECTED_RESULT_D8);
   }
 
@@ -64,13 +61,13 @@
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
+        .addInnerClassesAndStrippedOuter(getClass())
         .setMinApi(parameters)
-        .addKeepMainRule(MAIN_TYPE)
+        .addKeepMainRule(RecordShrinkField.class)
         .addDontObfuscateUnless(minifying)
         .compile()
         .inspect(inspector -> inspect(inspector, false))
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordShrinkField.class)
         .assertSuccessWithOutput(
             minifying ? EXPECTED_RESULT_R8 : EXPECTED_RESULT_R8_NO_MINIFICATION);
   }
@@ -80,28 +77,27 @@
     parameters.assumeR8TestParameters();
     Path desugared =
         testForR8(Backend.CF)
-            .addProgramClassFileData(PROGRAM_DATA)
-            .addKeepMainRule(MAIN_TYPE)
+            .addInnerClassesAndStrippedOuter(getClass())
+            .addKeepMainRule(RecordShrinkField.class)
             .addDontObfuscateUnless(minifying)
-            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+            .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
             .compile()
             .writeToZip();
     testForR8(parameters.getBackend())
         .addProgramFiles(desugared)
         .setMinApi(parameters)
-        .addKeepMainRule(MAIN_TYPE)
+        .addKeepMainRule(RecordShrinkField.class)
         .addDontObfuscateUnless(minifying)
         .compile()
         .inspect(inspector -> inspect(inspector, true))
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordShrinkField.class)
         .assertSuccessWithOutput(
             minifying ? EXPECTED_RESULT_R8 : EXPECTED_RESULT_R8_NO_MINIFICATION);
   }
 
   private void inspect(CodeInspector inspector, boolean isCfThenDex) {
-    ClassSubject recordClass =
-        inspector.clazz(minifying ? "records.a" : "records.RecordShrinkField$Person");
-    if (isCfThenDex || !minifying) {
+    ClassSubject recordClass = inspector.clazz(Person.class);
+    if (!isCfThenDex || !minifying) {
       assertEquals(1, recordClass.allInstanceFields().size());
       assertEquals(
           "java.lang.String", recordClass.allInstanceFields().get(0).getField().type().toString());
@@ -109,4 +105,20 @@
       assertEquals(0, recordClass.allInstanceFields().size());
     }
   }
+
+  record Person(int unused, String name, int age) {
+    Person(String name, int age) {
+      this(-1, name, age);
+    }
+  }
+
+  public class RecordShrinkField {
+
+    public static void main(String[] args) {
+      Person jane = new Person("Jane Doe", 42);
+      Person bob = new Person("Bob", 42);
+      System.out.println(jane);
+      System.out.println(bob);
+    }
+  }
 }
diff --git a/src/test/examplesJava17/records/RecordWithAnnotations.java b/src/test/examplesJava17/records/RecordWithAnnotations.java
deleted file mode 100644
index fe9fd95..0000000
--- a/src/test/examplesJava17/records/RecordWithAnnotations.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// 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 records;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.Field;
-import java.lang.reflect.RecordComponent;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-
-public class RecordWithAnnotations {
-
-  @Target({ElementType.FIELD, ElementType.RECORD_COMPONENT})
-  @Retention(RetentionPolicy.RUNTIME)
-  @interface Annotation {
-    String value();
-  }
-
-  @Target(ElementType.FIELD)
-  @Retention(RetentionPolicy.RUNTIME)
-  @interface AnnotationFieldOnly {
-    String value();
-  }
-
-  @Target(ElementType.RECORD_COMPONENT)
-  @Retention(RetentionPolicy.RUNTIME)
-  @interface AnnotationRecordComponentOnly {
-    String value();
-  }
-
-  record Person(
-      @Annotation("a") @AnnotationFieldOnly("b") @AnnotationRecordComponentOnly("c") String name,
-      @Annotation("x") @AnnotationFieldOnly("y") @AnnotationRecordComponentOnly("z") 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);
-    System.out.println(janeDoe.name());
-    System.out.println(janeDoe.age());
-    try {
-      Class.class.getDeclaredMethod("isRecord");
-    } catch (NoSuchMethodException e) {
-      System.out.println("Class.isRecord not present");
-      return;
-    }
-    System.out.println(Person.class.isRecord());
-    if (Person.class.isRecord()) {
-      System.out.println(Person.class.getRecordComponents().length);
-      for (int i = 0; i < Person.class.getRecordComponents().length; i++) {
-        RecordComponent c = Person.class.getRecordComponents()[i];
-        System.out.println(c.getName());
-        System.out.println(c.getType().getName());
-        System.out.println(c.getGenericSignature() == null);
-        System.out.println(c.getAnnotations().length);
-        // Collect and sort the annotations, as the order is not deterministic on Art (tested
-        // on Art 14 Beta 3).
-        List<String> annotations = new ArrayList<>();
-        for (int j = 0; j < c.getAnnotations().length; j++) {
-          annotations.add(c.getAnnotations()[j].toString());
-        }
-        annotations.sort(Comparator.naturalOrder());
-        for (int j = 0; j < annotations.size(); j++) {
-          System.out.println(annotations.get(j));
-        }
-      }
-      System.out.println(Person.class.getDeclaredFields().length);
-      List<Field> fields = new ArrayList<>();
-      for (int i = 0; i < Person.class.getDeclaredFields().length; i++) {
-        fields.add(Person.class.getDeclaredFields()[i]);
-      }
-      fields.sort(
-          new Comparator<Field>() {
-            @Override
-            public int compare(Field o1, Field o2) {
-              return o1.getName().compareTo(o2.getName());
-            }
-          });
-      for (int i = 0; i < fields.size(); i++) {
-        Field f = fields.get(i);
-        System.out.println(f.getDeclaredAnnotations().length);
-        List<String> annotations = new ArrayList<>();
-        for (int j = 0; j < f.getDeclaredAnnotations().length; j++) {
-          annotations.add(f.getAnnotations()[j].toString());
-        }
-        annotations.sort(Comparator.naturalOrder());
-        for (int j = 0; j < annotations.size(); j++) {
-          System.out.println(annotations.get(j));
-        }
-      }
-    }
-  }
-}
diff --git a/src/test/examplesJava17/records/RecordWithConstClass.java b/src/test/examplesJava17/records/RecordWithConstClass.java
deleted file mode 100644
index db06f51..0000000
--- a/src/test/examplesJava17/records/RecordWithConstClass.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// 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 records.differentpackage.PrivateConstClass;
-
-public class RecordWithConstClass {
-
-  record MyRecordWithConstClass(Class<?> theClass) { }
-
-  public static void main(String[] args) {
-    MyRecordWithConstClass inst =
-        new MyRecordWithConstClass(PrivateConstClass.getPrivateConstClass());
-    System.out.println(inst);
-  }
-}
diff --git a/src/test/examplesJava17/records/RecordWithMembers.java b/src/test/examplesJava17/records/RecordWithMembers.java
deleted file mode 100644
index 6de2b99..0000000
--- a/src/test/examplesJava17/records/RecordWithMembers.java
+++ /dev/null
@@ -1,69 +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 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/examplesJava17/records/RecordWithMembersTest.java b/src/test/examplesJava17/records/RecordWithMembersTest.java
new file mode 100644
index 0000000..41d36ea
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordWithMembersTest.java
@@ -0,0 +1,137 @@
+// 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 com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.StringUtils;
+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 EXPECTED_RESULT =
+      StringUtils.lines("BobX", "43", "FelixX", "-1", "print", "Bob43", "extra");
+
+  private final TestParameters parameters;
+
+  public RecordWithMembersTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+        .withDexRuntimes()
+        .withAllApiLevelsAlsoForCf()
+        .build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), RecordWithMembers.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), RecordWithMembers.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addInnerClassesAndStrippedOuter(getClass())
+            .setMinApi(parameters)
+            .addKeepMainRule(RecordWithMembers.class);
+    if (parameters.isCfRuntime()) {
+      builder
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+          .compile()
+          .inspect(RecordTestUtils::assertRecordsAreRecords)
+          .run(parameters.getRuntime(), RecordWithMembers.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      return;
+    }
+    builder
+        .run(parameters.getRuntime(), RecordWithMembers.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  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 class RecordWithMembers {
+
+    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);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java b/src/test/examplesJava17/records/RecordWithNonMaterializableConstClassTest.java
similarity index 67%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
rename to src/test/examplesJava17/records/RecordWithNonMaterializableConstClassTest.java
index bcf4294..4cda982 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
+++ b/src/test/examplesJava17/records/RecordWithNonMaterializableConstClassTest.java
@@ -2,9 +2,9 @@
 // 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;
+package records;
 
-
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -17,24 +17,19 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
+import records.differentpackage.PrivateConstClass;
 
 @RunWith(Parameterized.class)
 public class RecordWithNonMaterializableConstClassTest extends TestBase {
 
-  private static final String RECORD_NAME = "RecordWithConstClass";
   private static final String PRIVATE_CLASS_NAME =
       "records.differentpackage.PrivateConstClass$PrivateClass";
-  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
-  private static final byte[][] EXTRA_DATA =
-      RecordTestUtils.getProgramData("differentpackage/PrivateConstClass");
-  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final Class<?> EXTRA_DATA = PrivateConstClass.class;
   private static final String EXPECTED_RESULT_FORMAT =
       StringUtils.lines("%s[%s=class " + PRIVATE_CLASS_NAME + "]");
   private static final String EXPECTED_RESULT_D8 =
       String.format(EXPECTED_RESULT_FORMAT, "MyRecordWithConstClass", "theClass");
   private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT_FORMAT, "a", "a");
-  private static final String EXPECTED_RESULT_R8_ART14 =
-      String.format(EXPECTED_RESULT_FORMAT, "a", "theClass");
 
   @Parameter(0)
   public TestParameters parameters;
@@ -52,20 +47,22 @@
   public void testJvm() throws Exception {
     parameters.assumeJvmTestParameters();
     testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .addProgramClassFileData(EXTRA_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addProgramClasses(EXTRA_DATA)
+        .addInnerClasses(EXTRA_DATA)
+        .run(parameters.getRuntime(), RecordWithConstClass.class)
         .assertSuccessWithOutput(EXPECTED_RESULT_D8);
   }
 
   @Test
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        .addProgramClassFileData(EXTRA_DATA)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addProgramClasses(EXTRA_DATA)
+        .addInnerClasses(EXTRA_DATA)
         .setMinApi(parameters)
         .compile()
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordWithConstClass.class)
         .assertSuccessWithOutput(EXPECTED_RESULT_D8);
   }
 
@@ -73,12 +70,13 @@
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        .addProgramClassFileData(EXTRA_DATA)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addProgramClasses(EXTRA_DATA)
+        .addInnerClasses(EXTRA_DATA)
         .apply(this::configureR8)
         .setMinApi(parameters)
         .compile()
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordWithConstClass.class)
         .assertSuccessWithOutput(EXPECTED_RESULT_R8);
   }
 
@@ -87,9 +85,10 @@
     parameters.assumeR8TestParameters();
     Path desugared =
         testForR8(Backend.CF)
-            .addProgramClassFileData(PROGRAM_DATA)
-            .addProgramClassFileData(EXTRA_DATA)
-            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+            .addInnerClassesAndStrippedOuter(getClass())
+            .addProgramClasses(EXTRA_DATA)
+            .addInnerClasses(EXTRA_DATA)
+            .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
             .apply(this::configureR8)
             .compile()
             .writeToZip();
@@ -100,16 +99,27 @@
         .apply(this::configureR8)
         .setMinApi(parameters)
         .compile()
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), RecordWithConstClass.class)
         .assertSuccessWithOutput(EXPECTED_RESULT_R8);
   }
 
   private void configureR8(R8FullTestBuilder testBuilder) {
     testBuilder
-        .addKeepMainRule(MAIN_TYPE)
+        .addKeepMainRule(RecordWithConstClass.class)
         .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
         .applyIf(
             parameters.isCfRuntime(),
-            b -> b.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)));
+            b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()));
+  }
+
+  record MyRecordWithConstClass(Class<?> theClass) {}
+
+  public static class RecordWithConstClass {
+
+    public static void main(String[] args) {
+      MyRecordWithConstClass inst =
+          new MyRecordWithConstClass(PrivateConstClass.getPrivateConstClass());
+      System.out.println(inst);
+    }
   }
 }
diff --git a/src/test/examplesJava17/records/RecordWithSignature.java b/src/test/examplesJava17/records/RecordWithSignature.java
deleted file mode 100644
index be31024..0000000
--- a/src/test/examplesJava17/records/RecordWithSignature.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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 records;
-
-import java.lang.reflect.RecordComponent;
-
-public class RecordWithSignature {
-
-  record Person<N extends CharSequence, A>(N name, A age) {}
-
-  public static void main(String[] args) {
-    Person<String, Integer> 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());
-    try {
-      Class.class.getDeclaredMethod("isRecord");
-    } catch (NoSuchMethodException e) {
-      System.out.println("Class.isRecord not present");
-      return;
-    }
-    System.out.println(Person.class.isRecord());
-    if (Person.class.isRecord()) {
-      System.out.println(Person.class.getRecordComponents().length);
-      for (int i = 0; i < Person.class.getRecordComponents().length; i++) {
-        RecordComponent c = Person.class.getRecordComponents()[i];
-        System.out.println(c.getName());
-        System.out.println(c.getType().getName());
-        System.out.println(c.getGenericSignature());
-        System.out.println(c.getAnnotations().length);
-      }
-    }
-  }
-}
diff --git a/src/test/examplesJava17/records/SimpleRecord.java b/src/test/examplesJava17/records/SimpleRecord.java
deleted file mode 100644
index 3f908dc..0000000
--- a/src/test/examplesJava17/records/SimpleRecord.java
+++ /dev/null
@@ -1,35 +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 records;
-
-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);
-    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));
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/examplesJava17/records/SimpleRecordTest.java
similarity index 71%
rename from src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
rename to src/test/examplesJava17/records/SimpleRecordTest.java
index 9dbd8d2..d7c8131 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/examplesJava17/records/SimpleRecordTest.java
@@ -2,18 +2,18 @@
 // 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;
+package records;
 
-import static com.android.tools.r8.desugar.records.RecordTestUtils.assertNoJavaLangRecord;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -28,22 +28,9 @@
 @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",
-          "true",
-          "true",
-          "true",
-          "false",
-          "false",
-          "false",
-          "false");
+          "Jane Doe", "42", "true", "true", "true", "false", "false", "false", "false");
 
   @Parameter(0)
   public TestParameters parameters;
@@ -69,8 +56,8 @@
     assumeTrue(isCfRuntimeWithNativeRecordSupport());
     assumeFalse(forceInvokeRangeForInvokeCustom);
     testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), SimpleRecord.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
@@ -78,13 +65,13 @@
   public void testD8() throws Exception {
     assumeFalse(forceInvokeRangeForInvokeCustom);
     testForD8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
+        .addInnerClassesAndStrippedOuter(getClass())
         .setMinApi(parameters)
         .compile()
         .inspectWithOptions(
-            i -> assertNoJavaLangRecord(i, parameters),
+            i -> RecordTestUtils.assertNoJavaLangRecord(i, parameters),
             options -> options.testing.disableRecordApplicationReaderMap = true)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), SimpleRecord.class)
         .applyIf(
             isRecordsFullyDesugaredForD8(parameters)
                 || runtimeWithRecordsSupport(parameters.getRuntime()),
@@ -108,7 +95,7 @@
                     .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
         .setMinApi(parameters)
         .setIncludeClassesChecksum(true)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), SimpleRecord.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
@@ -129,14 +116,14 @@
         .setMinApi(parameters)
         .setIncludeClassesChecksum(true)
         .disableDesugaring()
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), SimpleRecord.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   private Path compileIntermediate(GlobalSyntheticsConsumer globalSyntheticsConsumer)
       throws Exception {
     return testForD8(Backend.DEX)
-        .addProgramClassFileData(PROGRAM_DATA)
+        .addInnerClassesAndStrippedOuter(getClass())
         .setMinApi(parameters)
         .setIntermediate(true)
         .setIncludeClassesChecksum(true)
@@ -156,25 +143,25 @@
                 opptions ->
                     opptions.testing.forceInvokeRangeForInvokeCustom =
                         forceInvokeRangeForInvokeCustom)
-            .addProgramClassFileData(PROGRAM_DATA)
+            .addInnerClassesAndStrippedOuter(getClass())
             .setMinApi(parameters)
-            .addKeepMainRule(MAIN_TYPE);
+            .addKeepMainRule(SimpleRecord.class);
     if (parameters.isCfRuntime()) {
       builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
           .compile()
           .inspect(RecordTestUtils::assertRecordsAreRecords)
-          .inspect(inspector -> inspector.clazz("records.SimpleRecord$Person").isRenamed())
-          .run(parameters.getRuntime(), MAIN_TYPE)
+          .inspect(inspector -> inspector.clazz(Person.class).isRenamed())
+          .run(parameters.getRuntime(), SimpleRecord.class)
           .assertSuccessWithOutput(EXPECTED_RESULT);
       return;
     }
     builder
         .compile()
         .inspectWithOptions(
-            i -> assertNoJavaLangRecord(i, parameters),
+            i -> RecordTestUtils.assertNoJavaLangRecord(i, parameters),
             options -> options.testing.disableRecordApplicationReaderMap = true)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), SimpleRecord.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
@@ -185,25 +172,53 @@
     assumeTrue(forceInvokeRangeForInvokeCustom || !parameters.isDexRuntime());
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA)
+            .addInnerClassesAndStrippedOuter(getClass())
             .addDontObfuscate()
             .setMinApi(parameters)
-            .addKeepMainRule(MAIN_TYPE);
+            .addKeepMainRule(SimpleRecord.class);
     if (parameters.isCfRuntime()) {
       builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
           .compile()
           .inspect(RecordTestUtils::assertRecordsAreRecords)
-          .run(parameters.getRuntime(), MAIN_TYPE)
+          .run(parameters.getRuntime(), SimpleRecord.class)
           .assertSuccessWithOutput(EXPECTED_RESULT);
       return;
     }
     builder
         .compile()
         .inspectWithOptions(
-            i -> assertNoJavaLangRecord(i, parameters),
+            i -> RecordTestUtils.assertNoJavaLangRecord(i, parameters),
             options -> options.testing.disableRecordApplicationReaderMap = true)
-        .run(parameters.getRuntime(), MAIN_TYPE)
+        .run(parameters.getRuntime(), SimpleRecord.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
+
+  record Person(String name, int age) {}
+
+  public class SimpleRecord {
+
+    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));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
index d3514ae..3ead5bf 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
@@ -16,13 +16,13 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 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.ThrowableConsumer;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelHorizontalMergeAndD8MergeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelHorizontalMergeAndD8MergeTest.java
index 4c99837..af120aa 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelHorizontalMergeAndD8MergeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelHorizontalMergeAndD8MergeTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -19,7 +20,6 @@
 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;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
index a4aa543..e321b56 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.SingleTestRunResult;
@@ -18,7 +19,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionAndroidApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionAndroidApiTest.java
index 2a7ebee..288a050 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionAndroidApiTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionAndroidApiTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8TestCompileResult;
@@ -19,7 +20,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java
index e212a7a..e7508c5 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -18,7 +19,6 @@
 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.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java
index 7b98e5c..898bd3e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -18,7 +19,6 @@
 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.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
index bb28672..eaa397f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
@@ -12,13 +12,13 @@
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 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.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;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
index 15ffd25..a67674e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
@@ -12,13 +12,13 @@
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 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.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;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoHorizontalMergeAndD8MergeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoHorizontalMergeAndD8MergeTest.java
index 880360d..a02fe01 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoHorizontalMergeAndD8MergeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoHorizontalMergeAndD8MergeTest.java
@@ -12,13 +12,13 @@
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 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.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;
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/CompilationDependentSetTest.java b/src/test/java/com/android/tools/r8/desugar/graph/CompilationDependentSetTest.java
index 258805a..c890f40 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/CompilationDependentSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/CompilationDependentSetTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.DesugarGraphTestConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
index 6c11011..bfd0048 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.DesugarGraphTestConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
index aab7123..42f49fb 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.DesugarGraphTestConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/LambdaDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/LambdaDependencyTest.java
index cdb01f6..37674a7 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/LambdaDependencyTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/LambdaDependencyTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.DesugarGraphTestConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/NestDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/NestDependencyTest.java
index df53dc1..4f97f34 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/NestDependencyTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/NestDependencyTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.DesugarGraphTestConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/Regress167562221Test.java b/src/test/java/com/android/tools/r8/desugar/graph/Regress167562221Test.java
index 650338c..629de48 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/Regress167562221Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/Regress167562221Test.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.DesugarGraphTestConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
deleted file mode 100644
index 41f97f8..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
+++ /dev/null
@@ -1,394 +0,0 @@
-// 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.desugar.records;
-
-import static com.android.tools.r8.utils.codeinspector.AnnotationMatchers.hasAnnotationTypes;
-import static com.android.tools.r8.utils.codeinspector.AnnotationMatchers.hasElements;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.FieldSubject;
-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 RecordComponentAnnotationsTest extends TestBase {
-
-  private static final String RECORD_NAME = "RecordWithAnnotations";
-  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
-  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
-  private static final String JVM_UNTIL_20_EXPECTED_RESULT =
-      StringUtils.lines(
-          "Jane Doe",
-          "42",
-          "Jane Doe",
-          "42",
-          "true",
-          "2",
-          "name",
-          "java.lang.String",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"a\")",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"c\")",
-          "age",
-          "int",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"x\")",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"z\")",
-          "2",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"x\")",
-          "@records.RecordWithAnnotations$AnnotationFieldOnly(\"y\")",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"a\")",
-          "@records.RecordWithAnnotations$AnnotationFieldOnly(\"b\")");
-  private static final String JVM_FROM_21_EXPECTED_RESULT =
-      JVM_UNTIL_20_EXPECTED_RESULT.replaceAll(
-          "RecordWithAnnotations\\$Annotation", "RecordWithAnnotations.Annotation");
-  private static final String ART_EXPECTED_RESULT =
-      StringUtils.lines(
-          "Jane Doe",
-          "42",
-          "Jane Doe",
-          "42",
-          "true",
-          "2",
-          "name",
-          "java.lang.String",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=a)",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(value=c)",
-          "age",
-          "int",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=x)",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(value=z)",
-          "2",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=x)",
-          "@records.RecordWithAnnotations$AnnotationFieldOnly(value=y)",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=a)",
-          "@records.RecordWithAnnotations$AnnotationFieldOnly(value=b)");
-  private static final String JVM_EXPECTED_RESULT_R8 =
-      StringUtils.lines(
-          "Jane Doe",
-          "42",
-          "Jane Doe",
-          "42",
-          "true",
-          "2",
-          "a",
-          "java.lang.String",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"a\")",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"c\")",
-          "b",
-          "int",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"x\")",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"z\")",
-          "2",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"a\")",
-          "@records.RecordWithAnnotations$AnnotationFieldOnly(\"b\")",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"x\")",
-          "@records.RecordWithAnnotations$AnnotationFieldOnly(\"y\")");
-  private static final String ART_EXPECTED_RESULT_R8 =
-      StringUtils.lines(
-          "Jane Doe",
-          "42",
-          "Jane Doe",
-          "42",
-          "true",
-          "2",
-          "a",
-          "java.lang.String",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=a)",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(value=c)",
-          "b",
-          "int",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=x)",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(value=z)",
-          "2",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=a)",
-          "@records.RecordWithAnnotations$AnnotationFieldOnly(value=b)",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=x)",
-          "@records.RecordWithAnnotations$AnnotationFieldOnly(value=y)");
-  private static final String JVM_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS =
-      StringUtils.lines(
-          "Jane Doe",
-          "42",
-          "Jane Doe",
-          "42",
-          "true",
-          "2",
-          "a",
-          "java.lang.String",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"a\")",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"c\")",
-          "b",
-          "int",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(\"x\")",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"z\")",
-          "2",
-          "0",
-          "0");
-  private static final String ART_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS =
-      StringUtils.lines(
-          "Jane Doe",
-          "42",
-          "Jane Doe",
-          "42",
-          "true",
-          "2",
-          "a",
-          "java.lang.String",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=a)",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(value=c)",
-          "b",
-          "int",
-          "true",
-          "2",
-          "@records.RecordWithAnnotations$Annotation(value=x)",
-          "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(value=z)",
-          "2",
-          "0",
-          "0");
-  private static final String EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT =
-      StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "false");
-  private static final String EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT =
-      StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "Class.isRecord not present");
-
-  @Parameter(0)
-  public TestParameters parameters;
-
-  @Parameter(1)
-  public Boolean keepAnnotations;
-
-  // Enable once records are no longer partially desugared on platform.
-  public boolean recordDesugaringIsOffOnDex = false;
-
-  @Parameters(name = "{0}, keepAnnotations: {1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters()
-            .withDexRuntimesAndAllApiLevels()
-            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
-            .withAllApiLevelsAlsoForCf()
-            .build(),
-        BooleanUtils.values());
-  }
-
-  @Test
-  public void testJvm() throws Exception {
-    parameters.assumeJvmTestParameters();
-    assumeTrue(keepAnnotations);
-    testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(
-            parameters.getRuntime().asCf().getVm().isLessThanOrEqualTo(CfVm.JDK20)
-                ? JVM_UNTIL_20_EXPECTED_RESULT
-                : JVM_FROM_21_EXPECTED_RESULT);
-  }
-
-  @Test
-  public void testDesugaring() throws Exception {
-    parameters.assumeDexRuntime();
-    assumeTrue(keepAnnotations);
-    testForDesugaring(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .applyIf(
-            parameters.isDexRuntime(),
-            r ->
-                r.assertSuccessWithOutput(
-                    runtimeWithRecordsSupport(parameters.getRuntime())
-                        ? EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT
-                        : EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT),
-            r ->
-                r.assertSuccessWithOutput(ART_EXPECTED_RESULT)
-                    .inspect(
-                        inspector -> {
-                          ClassSubject person =
-                              inspector.clazz("records.RecordWithAnnotations$Person");
-                          FieldSubject name = person.uniqueFieldWithOriginalName("name");
-                          assertThat(name, isPresentAndNotRenamed());
-                          FieldSubject age = person.uniqueFieldWithOriginalName("age");
-                          assertThat(age, isPresentAndNotRenamed());
-                          assertEquals(2, person.getFinalRecordComponents().size());
-
-                          assertEquals(
-                              name.getFinalName(),
-                              person.getFinalRecordComponents().get(0).getName());
-                          assertTrue(
-                              person
-                                  .getFinalRecordComponents()
-                                  .get(0)
-                                  .getType()
-                                  .is("java.lang.String"));
-                          assertNull(person.getFinalRecordComponents().get(0).getSignature());
-                          assertEquals(
-                              2, person.getFinalRecordComponents().get(0).getAnnotations().size());
-                          assertThat(
-                              person.getFinalRecordComponents().get(0).getAnnotations(),
-                              hasAnnotationTypes(
-                                  inspector.getTypeSubject(
-                                      "records.RecordWithAnnotations$Annotation"),
-                                  inspector.getTypeSubject(
-                                      "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
-                          assertThat(
-                              person.getFinalRecordComponents().get(0).getAnnotations().get(0),
-                              hasElements(new Pair<>("value", "a")));
-                          assertThat(
-                              person.getFinalRecordComponents().get(0).getAnnotations().get(1),
-                              hasElements(new Pair<>("value", "c")));
-
-                          assertEquals(
-                              age.getFinalName(),
-                              person.getFinalRecordComponents().get(1).getName());
-                          assertTrue(person.getFinalRecordComponents().get(1).getType().is("int"));
-                          assertNull(person.getFinalRecordComponents().get(1).getSignature());
-                          assertEquals(
-                              2, person.getFinalRecordComponents().get(1).getAnnotations().size());
-                          assertThat(
-                              person.getFinalRecordComponents().get(1).getAnnotations(),
-                              hasAnnotationTypes(
-                                  inspector.getTypeSubject(
-                                      "records.RecordWithAnnotations$Annotation"),
-                                  inspector.getTypeSubject(
-                                      "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
-                          assertThat(
-                              person.getFinalRecordComponents().get(1).getAnnotations().get(0),
-                              hasElements(new Pair<>("value", "x")));
-                          assertThat(
-                              person.getFinalRecordComponents().get(1).getAnnotations().get(1),
-                              hasElements(new Pair<>("value", "z")));
-                        }));
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    parameters.assumeR8TestParameters();
-    testForR8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        // TODO(b/231930852): Change to android.jar for Android U when that contains
-        // java.lang.Record.
-        .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-        .addKeepMainRule(MAIN_TYPE)
-        .addKeepClassAndMembersRulesWithAllowObfuscation("records.RecordWithAnnotations$Person")
-        .addKeepClassAndMembersRules(
-            "records.RecordWithAnnotations$Annotation",
-            "records.RecordWithAnnotations$AnnotationFieldOnly",
-            "records.RecordWithAnnotations$AnnotationRecordComponentOnly")
-        .applyIf(keepAnnotations, TestShrinkerBuilder::addKeepRuntimeVisibleAnnotations)
-        .setMinApi(parameters)
-        .compile()
-        .inspect(
-            inspector -> {
-              ClassSubject person = inspector.clazz("records.RecordWithAnnotations$Person");
-              FieldSubject name = person.uniqueFieldWithOriginalName("name");
-              FieldSubject age = person.uniqueFieldWithOriginalName("age");
-              if (parameters.isCfRuntime()) {
-                assertEquals(2, person.getFinalRecordComponents().size());
-
-                assertEquals(
-                    name.getFinalName(), person.getFinalRecordComponents().get(0).getName());
-                assertTrue(
-                    person.getFinalRecordComponents().get(0).getType().is("java.lang.String"));
-                assertNull(person.getFinalRecordComponents().get(0).getSignature());
-                assertEquals(2, person.getFinalRecordComponents().get(0).getAnnotations().size());
-                assertThat(
-                    person.getFinalRecordComponents().get(0).getAnnotations(),
-                    hasAnnotationTypes(
-                        inspector.getTypeSubject("records.RecordWithAnnotations$Annotation"),
-                        inspector.getTypeSubject(
-                            "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
-                assertThat(
-                    person.getFinalRecordComponents().get(0).getAnnotations().get(0),
-                    hasElements(new Pair<>("value", "a")));
-                assertThat(
-                    person.getFinalRecordComponents().get(0).getAnnotations().get(1),
-                    hasElements(new Pair<>("value", "c")));
-
-                assertEquals(
-                    age.getFinalName(), person.getFinalRecordComponents().get(1).getName());
-                assertTrue(person.getFinalRecordComponents().get(1).getType().is("int"));
-                assertNull(person.getFinalRecordComponents().get(1).getSignature());
-                assertEquals(2, person.getFinalRecordComponents().get(1).getAnnotations().size());
-                assertThat(
-                    person.getFinalRecordComponents().get(1).getAnnotations(),
-                    hasAnnotationTypes(
-                        inspector.getTypeSubject("records.RecordWithAnnotations$Annotation"),
-                        inspector.getTypeSubject(
-                            "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
-                assertThat(
-                    person.getFinalRecordComponents().get(1).getAnnotations().get(0),
-                    hasElements(new Pair<>("value", "x")));
-                assertThat(
-                    person.getFinalRecordComponents().get(1).getAnnotations().get(1),
-                    hasElements(new Pair<>("value", "z")));
-              } else {
-                assertEquals(0, person.getFinalRecordComponents().size());
-              }
-            })
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .applyIf(
-            // TODO(b/274888318): EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS still has component
-            //  annotations.
-            parameters.isCfRuntime(),
-            r ->
-                r.assertSuccessWithOutput(
-                    keepAnnotations
-                        ? JVM_EXPECTED_RESULT_R8
-                        : JVM_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS),
-            recordDesugaringIsOffOnDex,
-            r ->
-                r.assertSuccessWithOutput(
-                    keepAnnotations
-                        ? ART_EXPECTED_RESULT_R8
-                        : ART_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS),
-            r ->
-                r.assertSuccessWithOutput(
-                    runtimeWithRecordsSupport(parameters.getRuntime())
-                        ? EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT
-                        : EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT));
-  }
-}
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
deleted file mode 100644
index 8be5a1d..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// 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.R8FullTestBuilder;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.desugar.LibraryFilesHelper;
-import com.android.tools.r8.utils.StringUtils;
-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(
-          "%s[]",
-          "true",
-          "true",
-          "true",
-          "true",
-          "true",
-          "false",
-          "true",
-          "true",
-          "false",
-          "false",
-          "%s[%s=Jane Doe, %s=42]");
-  private static final String EXPECTED_RESULT_D8 =
-      String.format(EXPECTED_RESULT, "Empty", "Person", "name", "age");
-  private static final String EXPECTED_RESULT_R8 =
-      String.format(EXPECTED_RESULT, "a", "b", "a", "b");
-
-  private final TestParameters parameters;
-
-  public RecordInvokeCustomTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
-        .withDexRuntimes()
-        .withAllApiLevelsAlsoForCf()
-        .build();
-  }
-
-  @Test
-  public void testJvm() throws Exception {
-    parameters.assumeJvmTestParameters();
-    testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_D8);
-  }
-
-  @Test
-  public void testD8() throws Exception {
-    testForD8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        .setMinApi(parameters)
-        .compile()
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_D8);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    parameters.assumeR8TestParameters();
-    R8FullTestBuilder builder =
-        testForR8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA)
-            .setMinApi(parameters)
-            .addKeepMainRule(MAIN_TYPE);
-    if (parameters.isCfRuntime()) {
-      builder
-          .addLibraryFiles(LibraryFilesHelper.getJdk15LibraryFiles(temp))
-          .compile()
-          .inspect(RecordTestUtils::assertRecordsAreRecords)
-          .run(parameters.getRuntime(), MAIN_TYPE)
-          .assertSuccessWithOutput(EXPECTED_RESULT_R8);
-      return;
-    }
-    builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT_R8);
-  }
-}
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
deleted file mode 100644
index 6a63449..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.desugar.LibraryFilesHelper;
-import com.android.tools.r8.utils.StringUtils;
-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 TestParametersCollection data() {
-    return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK17).build();
-  }
-
-  @Test
-  public void testJvm() throws Exception {
-    parameters.assumeJvmTestParameters();
-    testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
-  }
-
-  @Test
-  public void testR8Cf() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        .setMinApi(parameters)
-        .addKeepMainRule(MAIN_TYPE)
-        .addKeepAllAttributes()
-        .addKeepRules("-keep class * extends java.lang.Record { private final <fields>; }")
-        .addLibraryFiles(LibraryFilesHelper.getJdk15LibraryFiles(temp))
-        .compile()
-        .inspect(RecordTestUtils::assertRecordsAreRecords)
-        .enableJVMPreview()
-        .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
deleted file mode 100644
index 1194f76..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// 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 org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.desugar.LibraryFilesHelper;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-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;
-import org.junit.rules.TemporaryFolder;
-
-/**
- * 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 = "examplesJava17";
-  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 Path[] getJdk15LibraryFiles(TemporaryFolder temp) throws Exception {
-    return LibraryFilesHelper.getJdk15LibraryFiles(temp);
-  }
-
-  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]);
-  }
-
-  public static void assertRecordsAreRecords(CodeInspector inspector) {
-    for (FoundClassSubject clazz : inspector.allClasses()) {
-      if (clazz.getDexProgramClass().superType.toString().equals("java.lang.Record")) {
-        assertTrue(clazz.getDexProgramClass().isRecord());
-      }
-    }
-  }
-
-  public static void assertNoJavaLangRecord(CodeInspector inspector, TestParameters parameters) {
-    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.V)) {
-      assertFalse(inspector.clazz("java.lang.RecordTag").isPresent());
-    } else {
-      assertFalse(inspector.clazz("java.lang.Record").isPresent());
-    }
-  }
-}
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
deleted file mode 100644
index e4c6be4..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.R8FullTestBuilder;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.StringUtils;
-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 TestParametersCollection data() {
-    return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
-        .withDexRuntimes()
-        .withAllApiLevelsAlsoForCf()
-        .build();
-  }
-
-  @Test
-  public void testJvm() throws Exception {
-    parameters.assumeJvmTestParameters();
-    testForJvm(parameters)
-        .addProgramClassFileData(PROGRAM_DATA)
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
-  }
-
-  @Test
-  public void testD8() throws Exception {
-    testForD8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        .setMinApi(parameters)
-        .compile()
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    parameters.assumeR8TestParameters();
-    R8FullTestBuilder builder =
-        testForR8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA)
-            .setMinApi(parameters)
-            .addKeepMainRule(MAIN_TYPE);
-    if (parameters.isCfRuntime()) {
-      builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-          .compile()
-          .inspect(RecordTestUtils::assertRecordsAreRecords)
-          .run(parameters.getRuntime(), MAIN_TYPE)
-          .assertSuccessWithOutput(EXPECTED_RESULT);
-      return;
-    }
-    builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
index d988c22..fcd2106 100644
--- a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
+++ b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
@@ -9,10 +9,10 @@
 
 import com.android.tools.r8.GlobalSyntheticsGenerator;
 import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
index d37c3f8..7853976 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
@@ -4,21 +4,14 @@
 
 package com.android.tools.r8.ir.desugar.annotations;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilation;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static junit.framework.TestCase.assertTrue;
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.AllOf.allOf;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -26,6 +19,7 @@
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -40,6 +34,7 @@
   public static final String CRTS_INNER_NAME = "CovariantReturnTypes";
   public static final String CRTS_BINARY_NAME = CRT_BINARY_NAME + "$" + CRTS_INNER_NAME;
 
+  public static final String CRT_TYPE_NAME = CRT_BINARY_NAME.replace('/', '.');
   public static final String CRTS_TYPE_NAME = CRT_BINARY_NAME.replace('/', '.');
 
   @Parameter(0)
@@ -105,8 +100,7 @@
     checkPresenceOfCovariantAnnotations(input, true);
 
     // Version 2 of the library should always work.
-    succeedsWithOption(input, true, true);
-    failsCompilationByDefault(input);
+    succeedsIndependentOfFlag(input, true);
   }
 
   @Test
@@ -130,7 +124,7 @@
 
     // If CovariantReturnType annotations are ignored, then there will be no methods with the
     // signatures "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;".
-    failsCompilationByDefault(input);
+    failsWithOption(input, false);
   }
 
   @Test
@@ -224,24 +218,7 @@
         .assertSuccessWithOutput(getExpectedOutput());
   }
 
-  private void failsCompilationByDefault(List<byte[]> input) throws Exception {
-    assertFailsCompilation(
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(input)
-                .setMinApi(parameters)
-                .compileWithExpectedDiagnostics(
-                    diagnostics ->
-                        diagnostics.assertErrorThatMatches(
-                            allOf(
-                                diagnosticType(StringDiagnostic.class),
-                                diagnosticMessage(
-                                    equalTo(
-                                        "Unexpected @CovariantReturnType annotation in non-platform"
-                                            + " build"))))));
-  }
-
-  private void failsRuntimeWithOption(List<byte[]> input, boolean option) throws Exception {
+  private void failsWithOption(List<byte[]> input, boolean option) throws Exception {
     testForD8(parameters.getBackend())
         .addProgramClassFileData(input)
         .addOptionsModification(options -> options.processCovariantReturnTypeAnnotations = option)
@@ -259,8 +236,8 @@
   }
 
   private void failsIndependentOfFlag(List<byte[]> input) throws Exception {
-    failsRuntimeWithOption(input, true);
-    failsRuntimeWithOption(input, false);
+    failsWithOption(input, true);
+    failsWithOption(input, false);
   }
 
   private void checkPresenceOfCovariantAnnotations(List<byte[]> input, boolean expected)
@@ -294,34 +271,34 @@
     MethodSubject methodA =
         clazzA.method(A.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodA, isPresent());
-    assertTrue(!methodA.getMethod().isSyntheticMethod());
+    Assert.assertTrue(!methodA.getMethod().isSyntheticMethod());
 
     MethodSubject methodB =
         clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodB, isPresent());
-    assertTrue(!methodB.getMethod().isSyntheticMethod());
+    Assert.assertTrue(!methodB.getMethod().isSyntheticMethod());
 
     MethodSubject methodC =
         clazzC.method(A.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodC, isPresent());
-    assertTrue(!methodC.getMethod().isSyntheticMethod());
+    Assert.assertTrue(!methodC.getMethod().isSyntheticMethod());
 
     // Check that a synthetic method has been added to class B.
     MethodSubject methodB2 =
         clazzB.method(B.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodB2, isPresent());
-    assertTrue(methodB2.getMethod().isSyntheticMethod());
+    Assert.assertTrue(methodB2.getMethod().isSyntheticMethod());
 
     // Check that two synthetic methods have been added to class C.
     MethodSubject methodC2 =
         clazzC.method(B.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodC2, isPresent());
-    assertTrue(methodC2.getMethod().isSyntheticMethod());
+    Assert.assertTrue(methodC2.getMethod().isSyntheticMethod());
 
     MethodSubject methodC3 =
         clazzC.method(C.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodC3, isPresent());
-    assertTrue(methodC3.getMethod().isSyntheticMethod());
+    Assert.assertTrue(methodC3.getMethod().isSyntheticMethod());
 
     // Get classes D, E, and F.
     ClassSubject clazzD = inspector.clazz(D.class.getCanonicalName());
@@ -337,34 +314,34 @@
     MethodSubject methodD =
         clazzD.method(D.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodD, isPresent());
-    assertTrue(!methodD.getMethod().isSyntheticMethod());
+    Assert.assertTrue(!methodD.getMethod().isSyntheticMethod());
 
     MethodSubject methodE =
         clazzE.method(D.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodE, isPresent());
-    assertTrue(!methodE.getMethod().isSyntheticMethod());
+    Assert.assertTrue(!methodE.getMethod().isSyntheticMethod());
 
     MethodSubject methodF =
         clazzF.method(D.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodF, isPresent());
-    assertTrue(!methodF.getMethod().isSyntheticMethod());
+    Assert.assertTrue(!methodF.getMethod().isSyntheticMethod());
 
     // Check that a synthetic method has been added to class E.
     MethodSubject methodE2 =
         clazzE.method(E.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodE2, isPresent());
-    assertTrue(methodE2.getMethod().isSyntheticMethod());
+    Assert.assertTrue(methodE2.getMethod().isSyntheticMethod());
 
     // Check that two synthetic methods have been added to class F.
     MethodSubject methodF2 =
         clazzF.method(E.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodF2, isPresent());
-    assertTrue(methodF2.getMethod().isSyntheticMethod());
+    Assert.assertTrue(methodF2.getMethod().isSyntheticMethod());
 
     MethodSubject methodF3 =
         clazzF.method(F.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodF3, isPresent());
-    assertTrue(methodF3.getMethod().isSyntheticMethod());
+    Assert.assertTrue(methodF3.getMethod().isSyntheticMethod());
   }
 
   private String getExpectedOutput() {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index f921cd6..8d9cc75 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -3,13 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_7_0;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
@@ -60,7 +64,16 @@
             .allowUnusedDontWarnJavaLangClassValue(
                 kotlinc.getCompilerVersion().isGreaterThan(KOTLINC_1_7_0))
             .apply(configureForLibraryWithEmbeddedProguardRules())
-            .compile()
+            .applyIf(kotlinc.is(KOTLINC_1_4_20), R8TestBuilder::allowDiagnosticWarningMessages)
+            .compileWithExpectedDiagnostics(
+                diagnostics -> {
+                  if (kotlinc.is(KOTLINC_1_4_20)) {
+                    diagnostics.assertWarningsMatch(
+                        diagnosticMessage(
+                            containsString(
+                                "'META-INF/versions/9/module-info.class' already exists.")));
+                  }
+                })
             .assertNoErrorMessages()
             .run(parameters.getRuntime(), mainClassName);
     CodeInspector inspector = result.inspector();
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index 7a54089..2149835 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -115,7 +115,9 @@
                 && kotlinParameters.getCompiler().isNot(KOTLINC_1_9_21)
                 && !kotlinParameters.isKotlinDev(),
             TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
-        .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
+        .applyIf(
+            kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72),
+            KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
         .writeToZip(foo.toPath())
         .run(parameters.getRuntime(), PKG + ".SimpleReflectKt")
         .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
diff --git a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticStubContextRegressionTest.java b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticStubContextRegressionTest.java
index 54f5f0c..9a56493 100644
--- a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticStubContextRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticStubContextRegressionTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java b/src/test/testbase/java/com/android/tools/r8/DesugarGraphTestConsumer.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
rename to src/test/testbase/java/com/android/tools/r8/DesugarGraphTestConsumer.java
index 541bfea..29efc40 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
+++ b/src/test/testbase/java/com/android/tools/r8/DesugarGraphTestConsumer.java
@@ -1,13 +1,12 @@
 // Copyright (c) 2019, 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.graph;
+package com.android.tools.r8;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.origin.GlobalSyntheticOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.StringUtils;
diff --git a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsTestingConsumer.java b/src/test/testbase/java/com/android/tools/r8/GlobalSyntheticsTestingConsumer.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsTestingConsumer.java
rename to src/test/testbase/java/com/android/tools/r8/GlobalSyntheticsTestingConsumer.java
index 9f1fdaf..da3905b 100644
--- a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsTestingConsumer.java
+++ b/src/test/testbase/java/com/android/tools/r8/GlobalSyntheticsTestingConsumer.java
@@ -1,17 +1,13 @@
 // 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.synthesis.globals;
+package com.android.tools.r8;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.GlobalSyntheticsConsumer;
-import com.android.tools.r8.GlobalSyntheticsResourceProvider;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
 import java.io.ByteArrayInputStream;
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBaseBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBaseBuilder.java
index 6746b89..98cb209 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBaseBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBaseBuilder.java
@@ -4,12 +4,16 @@
 
 package com.android.tools.r8;
 
+import static com.android.tools.r8.TestBase.descriptor;
+
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dump.CompilerDump;
 import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableMap;
@@ -35,6 +39,19 @@
     this.builder = builder;
   }
 
+  public T addStrippedOuter(Class<?> clazz, Origin origin) throws IOException {
+    builder.addClassProgramData(
+        TestBase.transformer(clazz)
+            .removeFields(FieldPredicate.all())
+            .removeMethods(MethodPredicate.all())
+            .removeAllAnnotations()
+            .setSuper(descriptor(Object.class))
+            .setImplements()
+            .transform(),
+        origin);
+    return self();
+  }
+
   @Override
   public T addProgramClassFileData(Collection<byte[]> classes) {
     for (byte[] clazz : classes) {
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
index 5f9d323..4b18fcb 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
@@ -17,6 +17,8 @@
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
@@ -212,6 +214,21 @@
     return self();
   }
 
+  public T addClasspathClassesAndInnerClasses(Class<?> clazz)
+      throws InvocationTargetException, IllegalAccessException {
+    Method getNestMembers;
+    try {
+      getNestMembers = Class.class.getDeclaredMethod("getNestMembers");
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException("Can only use this method from Java 11 and above.");
+    }
+    Class<?>[] nestMembers = (Class<?>[]) getNestMembers.invoke(clazz);
+    Arrays.stream(nestMembers)
+        .filter(c -> c.toString().startsWith(clazz.toString()))
+        .forEach(this::addClasspathClasses);
+    return self();
+  }
+
   public T addClasspathClasses(Class<?>... classes) {
     return addClasspathClasses(Arrays.asList(classes));
   }
diff --git a/tools/perf/chart.js b/tools/perf/chart.js
index 96c7f07..baaa33b 100644
--- a/tools/perf/chart.js
+++ b/tools/perf/chart.js
@@ -69,6 +69,9 @@
   } else {
     update(false, false);
   }
+  document.addEventListener('keydown', e => {
+    state.handleKeyDownEvent(e, () => update(true, false));
+  });
 }
 
 function setDataProvider(theDataProvider) {
diff --git a/tools/perf/state.js b/tools/perf/state.js
index 4d0128f..ed07b39 100644
--- a/tools/perf/state.js
+++ b/tools/perf/state.js
@@ -99,6 +99,34 @@
   }
 }
 
+function handleKeyDownEvent(e, callback) {
+  if (selectedBenchmarks.size != 1) {
+    return;
+  }
+  const [selectedBenchmark] = selectedBenchmarks;
+  var benchmarkToSelect = null;
+  var previousBenchmark = null;
+  for (const benchmark of benchmarks.values()) {
+    if (previousBenchmark != null) {
+      if (e.key == 'ArrowLeft' && benchmark == selectedBenchmark) {
+        benchmarkToSelect = previousBenchmark;
+        break;
+      } else if (e.key === 'ArrowRight' && previousBenchmark == selectedBenchmark) {
+        benchmarkToSelect = benchmark;
+        break;
+      }
+    }
+    previousBenchmark = benchmark;
+  }
+  if (benchmarkToSelect != null) {
+    selectedBenchmarks.clear();
+    selectedBenchmarks.add(benchmarkToSelect);
+    document.getElementById(selectedBenchmark).checked = false;
+    document.getElementById(benchmarkToSelect).checked = true;
+    callback();
+  }
+}
+
 function isLegendSelected(legend) {
   return selectedLegends.has(legend);
 }
@@ -111,6 +139,7 @@
   selectedLegends: selectedLegends,
   forEachBenchmark: forEachBenchmark,
   forEachSelectedBenchmark: forEachSelectedBenchmark,
+  handleKeyDownEvent: handleKeyDownEvent,
   hasLegend: hasLegend,
   initializeBenchmarks: initializeBenchmarks,
   initializeLegends: initializeLegends,