Add support for accepting art profile in CLI

Change-Id: I82864f1f096969d382d1daeb822071762add289e
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 1ba0b22..7599bb6 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -20,6 +20,7 @@
 public class BaseCompilerCommandParser<
     C extends BaseCompilerCommand, B extends BaseCompilerCommand.Builder<C, B>> {
 
+  protected static final String ART_PROFILE_FLAG = "--art-profile";
   protected static final String MIN_API_FLAG = "--min-api";
   protected static final String THREAD_COUNT_FLAG = "--thread-count";
   protected static final String MAP_DIAGNOSTICS = "--map-diagnostics";
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 0336de9..444f522 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.experimental.startup.StartupProfileProviderUtils;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
+import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -27,7 +29,7 @@
 
   static final String STARTUP_PROFILE_FLAG = "--startup-profile";
 
-  private static final Set<String> OPTIONS_WITH_PARAMETER =
+  private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
       ImmutableSet.of(
           "--output",
           "--lib",
@@ -41,8 +43,12 @@
           "--desugared-lib",
           "--desugared-lib-pg-conf-output",
           THREAD_COUNT_FLAG,
+          ART_PROFILE_FLAG,
           STARTUP_PROFILE_FLAG);
 
+  // Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER.
+  private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of(ART_PROFILE_FLAG);
+
   public static List<ParseFlagInfo> getFlags() {
     return ImmutableList.<ParseFlagInfo>builder()
         .add(ParseFlagInfoImpl.getDebug(true))
@@ -83,6 +89,7 @@
         .add(ParseFlagInfoImpl.getThreadCount())
         .add(ParseFlagInfoImpl.getMapDiagnostics())
         .add(ParseFlagInfoImpl.getAndroidPlatformBuild())
+        .add(ParseFlagInfoImpl.getArtProfile())
         .add(ParseFlagInfoImpl.getStartupProfile())
         .add(ParseFlagInfoImpl.getVersion("d8"))
         .add(ParseFlagInfoImpl.getHelp())
@@ -211,7 +218,8 @@
     for (int i = 0; i < expandedArgs.length; i++) {
       String arg = expandedArgs[i].trim();
       String nextArg = null;
-      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+      String nextNextArg = null;
+      if (OPTIONS_WITH_ONE_PARAMETER.contains(arg)) {
         if (++i < expandedArgs.length) {
           nextArg = expandedArgs[i];
         } else {
@@ -219,6 +227,15 @@
               new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
           break;
         }
+        if (OPTIONS_WITH_TWO_PARAMETERS.contains(arg)) {
+          if (++i < expandedArgs.length) {
+            nextNextArg = expandedArgs[i];
+          } else {
+            builder.error(
+                new StringDiagnostic("Missing parameter for " + expandedArgs[i - 2] + ".", origin));
+            break;
+          }
+        }
       }
       if (arg.length() == 0) {
         continue;
@@ -310,6 +327,12 @@
         builder.setDesugaredLibraryKeepRuleConsumer(consumer);
       } else if (arg.equals("--android-platform-build")) {
         builder.setAndroidPlatformBuild(true);
+      } else if (arg.equals(ART_PROFILE_FLAG)) {
+        Path artProfilePath = Paths.get(nextArg);
+        Path rewrittenArtProfilePath = Paths.get(nextNextArg);
+        builder.addArtProfileForRewriting(
+            ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfilePath),
+            ArtProfileConsumerUtils.create(rewrittenArtProfilePath));
       } else if (arg.equals(STARTUP_PROFILE_FLAG)) {
         Path startupProfilePath = Paths.get(nextArg);
         builder.addStartupProfileProviders(
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 94440be..bfe119b 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -163,6 +163,11 @@
   }
 
   @Override
+  List<ArtProfileForRewriting> getArtProfilesForRewriting() {
+    return getD8Command().getArtProfilesForRewriting();
+  }
+
+  @Override
   InternalOptions getInternalOptions() {
     InternalOptions internal = new InternalOptions(factory, getReporter());
     assert !internal.debug;
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 4603160..af389fe 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
+import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
 import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
@@ -18,7 +20,7 @@
 
 public class L8CommandParser extends BaseCompilerCommandParser<L8Command, L8Command.Builder> {
 
-  private static final Set<String> OPTIONS_WITH_PARAMETER =
+  private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
       ImmutableSet.of(
           "--output",
           "--lib",
@@ -26,7 +28,11 @@
           "--desugared-lib",
           THREAD_COUNT_FLAG,
           "--pg-conf",
-          "--pg-map-output");
+          "--pg-map-output",
+          ART_PROFILE_FLAG);
+
+  // Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER.
+  private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of(ART_PROFILE_FLAG);
 
   public static void main(String[] args) throws CompilationFailedException {
     L8Command command = parse(args, Origin.root()).build();
@@ -62,6 +68,7 @@
         .addAll(ParseFlagInfoImpl.getAssertionsFlags())
         .add(ParseFlagInfoImpl.getThreadCount())
         .add(ParseFlagInfoImpl.getMapDiagnostics())
+        .add(ParseFlagInfoImpl.getArtProfile())
         .add(ParseFlagInfoImpl.getVersion("l8"))
         .add(ParseFlagInfoImpl.getHelp())
         .build();
@@ -105,7 +112,8 @@
     for (int i = 0; i < expandedArgs.length; i++) {
       String arg = expandedArgs[i].trim();
       String nextArg = null;
-      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+      String nextNextArg = null;
+      if (OPTIONS_WITH_ONE_PARAMETER.contains(arg)) {
         if (++i < expandedArgs.length) {
           nextArg = expandedArgs[i];
         } else {
@@ -113,6 +121,15 @@
               new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
           break;
         }
+        if (OPTIONS_WITH_TWO_PARAMETERS.contains(arg)) {
+          if (++i < expandedArgs.length) {
+            nextNextArg = expandedArgs[i];
+          } else {
+            builder.error(
+                new StringDiagnostic("Missing parameter for " + expandedArgs[i - 2] + ".", origin));
+            break;
+          }
+        }
       }
       if (arg.length() == 0) {
         continue;
@@ -162,6 +179,12 @@
         builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
       } else if (arg.equals("--classfile")) {
         outputMode = OutputMode.ClassFile;
+      } else if (arg.equals(ART_PROFILE_FLAG)) {
+        Path artProfilePath = Paths.get(nextArg);
+        Path rewrittenArtProfilePath = Paths.get(nextNextArg);
+        builder.addArtProfileForRewriting(
+            ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfilePath),
+            ArtProfileConsumerUtils.create(rewrittenArtProfilePath));
       } else if (arg.equals(THREAD_COUNT_FLAG)) {
         parsePositiveIntArgument(
             builder::error, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
diff --git a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
index 595a0b0..ef42ebd 100644
--- a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.BaseCompilerCommandParser.ART_PROFILE_FLAG;
 import static com.android.tools.r8.BaseCompilerCommandParser.MAP_DIAGNOSTICS;
 import static com.android.tools.r8.BaseCompilerCommandParser.MIN_API_FLAG;
 import static com.android.tools.r8.BaseCompilerCommandParser.THREAD_COUNT_FLAG;
@@ -164,6 +165,14 @@
         "is assumed to be the version specified by --min-api.");
   }
 
+  public static ParseFlagInfoImpl getArtProfile() {
+    return flag2(
+        ART_PROFILE_FLAG,
+        "<input>",
+        "<output>",
+        "Rewrite human readable ART profile read from <input> and write to <output>.");
+  }
+
   public static ParseFlagInfoImpl getStartupProfile() {
     return flag1(STARTUP_PROFILE_FLAG, "<file>", "Startup profile <file> to use for dex layout.");
   }
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 9ffcdd2..f7db94e 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -9,6 +9,8 @@
 
 import com.android.tools.r8.StringConsumer.FileConsumer;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
+import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
 import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.MapIdTemplateProvider;
 import com.android.tools.r8.utils.SourceFileTemplateProvider;
@@ -27,7 +29,7 @@
 public class R8CommandParser extends BaseCompilerCommandParser<R8Command, R8Command.Builder> {
 
   // Note: this must be a super-set of OPTIONS_WITH_TWO_PARAMETERS.
-  private static final Set<String> OPTIONS_WITH_PARAMETER =
+  private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
       ImmutableSet.of(
           "--output",
           "--lib",
@@ -44,10 +46,12 @@
           "--desugared-lib-pg-conf-output",
           "--map-id-template",
           "--source-file-template",
+          ART_PROFILE_FLAG,
           THREAD_COUNT_FLAG);
 
   // Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER.
-  private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of("--feature");
+  private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS =
+      ImmutableSet.of(ART_PROFILE_FLAG, "--feature");
 
   // Due to the family of flags (for assertions and diagnostics) we can't base the one/two args
   // on this setup of flags. Thus, the flag collection just encodes the descriptive content.
@@ -104,6 +108,7 @@
                 "  %MAP_ID: map id (e.g., value of --map-id-template).",
                 "  %MAP_HASH: compiler generated mapping hash."))
         .add(ParseFlagInfoImpl.getAndroidPlatformBuild())
+        .add(ParseFlagInfoImpl.getArtProfile())
         .add(ParseFlagInfoImpl.getVersion("r8"))
         .add(ParseFlagInfoImpl.getHelp())
         .build();
@@ -178,7 +183,7 @@
       String arg = expandedArgs[i].trim();
       String nextArg = null;
       String nextNextArg = null;
-      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+      if (OPTIONS_WITH_ONE_PARAMETER.contains(arg)) {
         if (++i < expandedArgs.length) {
           nextArg = expandedArgs[i];
         } else {
@@ -301,6 +306,12 @@
             SourceFileTemplateProvider.create(nextArg, builder.getReporter()));
       } else if (arg.equals("--android-platform-build")) {
         builder.setAndroidPlatformBuild(true);
+      } else if (arg.equals(ART_PROFILE_FLAG)) {
+        Path artProfilePath = Paths.get(nextArg);
+        Path rewrittenArtProfilePath = Paths.get(nextNextArg);
+        builder.addArtProfileForRewriting(
+            ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfilePath),
+            ArtProfileConsumerUtils.create(rewrittenArtProfilePath));
       } else if (arg.startsWith("--")) {
         if (tryParseAssertionArgument(builder, arg, argsOrigin)) {
           continue;
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
index 80a9913..c55157d 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -80,6 +80,10 @@
     return (ArtProfileMethodRule) rules.get(method);
   }
 
+  public int size() {
+    return rules.size();
+  }
+
   public ArtProfile rewrittenWithLens(AppView<?> appView, GraphLens lens) {
     if (lens.isEnumUnboxerLens()) {
       return rewrittenWithLens(appView, lens.asEnumUnboxerLens());
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumerUtils.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumerUtils.java
new file mode 100644
index 0000000..bd7c680
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumerUtils.java
@@ -0,0 +1,34 @@
+// 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.profile.art;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TextOutputStream;
+import com.android.tools.r8.utils.UTF8TextOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+
+public class ArtProfileConsumerUtils {
+
+  public static ArtProfileConsumer create(Path rewrittenArtProfile) {
+    return new ArtProfileConsumer() {
+
+      @Override
+      public TextOutputStream getHumanReadableArtProfileConsumer() {
+        try {
+          return new UTF8TextOutputStream(rewrittenArtProfile);
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+        // Intentionally empty.
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java
new file mode 100644
index 0000000..bc3cbb5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java
@@ -0,0 +1,36 @@
+// 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.profile.art;
+
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.UTF8TextInputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+
+public class ArtProfileProviderUtils {
+
+  public static ArtProfileProvider createFromHumanReadableArtProfile(Path artProfile) {
+    return new ArtProfileProvider() {
+      @Override
+      public void getArtProfile(ArtProfileBuilder profileBuilder) {
+        try {
+          profileBuilder.addHumanReadableArtProfile(
+              new UTF8TextInputStream(artProfile), emptyConsumer());
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+      }
+
+      @Override
+      public Origin getOrigin() {
+        return new PathOrigin(artProfile);
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/UTF8TextOutputStream.java b/src/main/java/com/android/tools/r8/utils/UTF8TextOutputStream.java
new file mode 100644
index 0000000..10ced48
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/UTF8TextOutputStream.java
@@ -0,0 +1,36 @@
+// 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.utils;
+
+import com.android.tools.r8.TextOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class UTF8TextOutputStream implements TextOutputStream {
+
+  private final OutputStream outputStream;
+
+  public UTF8TextOutputStream(Path path) throws IOException {
+    this(Files.newOutputStream(path));
+  }
+
+  public UTF8TextOutputStream(OutputStream outputStream) {
+    this.outputStream = outputStream;
+  }
+
+  @Override
+  public OutputStream getOutputStream() {
+    return outputStream;
+  }
+
+  @Override
+  public Charset getCharset() {
+    return StandardCharsets.UTF_8;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/CommandTestBase.java b/src/test/java/com/android/tools/r8/CommandTestBase.java
index ed755f9..8fcdb4c 100644
--- a/src/test/java/com/android/tools/r8/CommandTestBase.java
+++ b/src/test/java/com/android/tools/r8/CommandTestBase.java
@@ -5,13 +5,22 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.profile.art.ArtProfile;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileForRewriting;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
 import org.junit.Test;
 
 public abstract class CommandTestBase<C extends BaseCompilerCommand> extends TestBase {
@@ -285,6 +294,87 @@
         });
   }
 
+  @Test
+  public void artProfileFlagAbsentTest() throws Exception {
+    assertTrue(parseWithRequiredArgs().getArtProfilesForRewriting().isEmpty());
+  }
+
+  @Test
+  public void artProfileFlagPresentTest() throws Exception {
+    // Create a simple profile.
+    Path profile = temp.newFile("profile.txt").toPath();
+    Path residualProfile = temp.newFile("profile-out.txt").toPath();
+    String profileRuleFlags = "HSP";
+    String profileRuleDescriptor = "Lfoo/bar/Baz;->qux()V";
+    FileUtils.writeTextFile(profile, profileRuleFlags + profileRuleDescriptor);
+
+    // Pass the profile on the command line.
+    List<ArtProfileForRewriting> artProfilesForRewriting =
+        parseWithRequiredArgs("--art-profile", profile.toString(), residualProfile.toString())
+            .getArtProfilesForRewriting();
+    assertEquals(1, artProfilesForRewriting.size());
+
+    // Extract inputs.
+    ArtProfileForRewriting artProfileForRewriting = artProfilesForRewriting.get(0);
+    ArtProfileProvider artProfileProvider = artProfileForRewriting.getArtProfileProvider();
+    ArtProfileConsumer residualArtProfileConsumer =
+        artProfileForRewriting.getResidualArtProfileConsumer();
+    InternalOptions options = new InternalOptions();
+
+    // Build provided ART profile.
+    ArtProfile.Builder artProfileBuilder =
+        ArtProfile.builderForInitialArtProfile(artProfileProvider, options);
+    artProfileProvider.getArtProfile(artProfileBuilder);
+    ArtProfile artProfile = artProfileBuilder.build();
+
+    // Verify we found the same rule.
+    assertEquals(1, artProfile.size());
+    IntBox count = new IntBox();
+    artProfile.forEachRule(
+        classRule -> fail(),
+        methodRule -> {
+          assertEquals(profileRuleDescriptor, methodRule.getMethod().toSmaliString());
+          assertTrue(methodRule.getMethodRuleInfo().isHot());
+          assertTrue(methodRule.getMethodRuleInfo().isStartup());
+          assertTrue(methodRule.getMethodRuleInfo().isPostStartup());
+          count.increment();
+        });
+    assertEquals(1, count.get());
+
+    // Supply the rule back to the consumer.
+    artProfile.supplyConsumer(residualArtProfileConsumer, options.reporter);
+    assertEquals(
+        ImmutableList.of(profileRuleFlags + profileRuleDescriptor),
+        FileUtils.readAllLines(residualProfile));
+  }
+
+  @Test
+  public void artProfileFlagMissingInputOutputParameterTest() {
+    String expectedErrorContains = "Missing parameter for --art-profile.";
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          expectedErrorContains, handler -> parseWithRequiredArgs(handler, "--art-profile"));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void artProfileFlagMissingOutputParameterTest() throws Exception {
+    String expectedErrorContains = "Missing parameter for --art-profile.";
+    Path profile = temp.newFile("profile.txt").toPath();
+    FileUtils.writeTextFile(profile, "");
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          expectedErrorContains,
+          handler -> parseWithRequiredArgs(handler, "--art-profile", profile.toString()));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
   private String[] prepareArgs(String[] args) {
     String[] actualTestArgs;
     String[] additionalTestArgs = requiredArgsForTest();