Test all --help messages (and fix platform newlines).

Change-Id: I143557d446a67950961ca743bb7fe283742cdb2f
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 798f0f6..e2e6e01 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.utils.internal.StringUtils;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.io.IOException;
@@ -49,11 +49,12 @@
       }
     }
 
-    static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
-        "Usage: dexsegments [options] <input-files>",
-        " where <input-files> are dex files",
-        "  --version               # Print the version of r8.",
-        "  --help                  # Print this message."));
+    static final String USAGE_MESSAGE =
+        StringUtils.joinLines(
+            "Usage: dexsegments [options] <input-files>",
+            " where <input-files> are dex files",
+            "  --version               # Print the version of r8.",
+            "  --help                  # Print this message.");
 
     public static Command.Builder builder() {
       return new Command.Builder();
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index ccfd8fd..67d3231 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -16,13 +16,14 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.internal.ConsumerUtils;
 import com.android.tools.r8.utils.FieldReferenceUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.ProgramResourceProviderUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.internal.ConsumerUtils;
+import com.android.tools.r8.utils.internal.StringUtils;
 import com.android.tools.r8.utils.timing.Timing;
 import java.io.Closeable;
 import java.io.IOException;
@@ -141,24 +142,25 @@
     }
 
     static final String USAGE_MESSAGE =
-        "Usage: disasm [options] <input-files>\n"
-            + " where <input-files> are dex files\n"
-            + " and options are:\n"
-            + "  --all                       # Include all information in disassembly.\n"
-            + "  --smali                     # Disassemble using smali syntax.\n"
-            + "  --ir                        # Print IR before and after optimization.\n"
-            + "  --nocode                    # No printing of code objects.\n"
-            + "  --pg-map <file>             # Proguard map <file> for mapping names.\n"
-            + "  --pg-map-charset <charset>  # Charset for Proguard map file.\n"
-            + "  --output                    # Specify a file or directory to write to.\n"
-            + "  --class <descriptor>        # Only disassemble the given class "
-            + "(e.g., Lcom/example/Class;).\n"
-            + "  --field <descriptor>        # Only disassemble the given field "
-            + "(e.g., Lcom/example/Class;->method()V).\n"
-            + "  --method <descriptor>       # Only disassemble the given method "
-            + "(e.g., Lcom/example/Class;->field:I).\n"
-            + "  --version                   # Print the version of r8.\n"
-            + "  --help                      # Print this message.";
+        StringUtils.joinLines(
+            "Usage: disasm [options] <input-files>",
+            " where <input-files> are dex files",
+            " and options are:",
+            "  --all                       # Include all information in disassembly.",
+            "  --smali                     # Disassemble using smali syntax.",
+            "  --ir                        # Print IR before and after optimization.",
+            "  --nocode                    # No printing of code objects.",
+            "  --pg-map <file>             # Proguard map <file> for mapping names.",
+            "  --pg-map-charset <charset>  # Charset for Proguard map file.",
+            "  --output                    # Specify a file or directory to write to.",
+            "  --class <descriptor>        # Only disassemble the given class (e.g.,"
+                + " Lcom/example/Class;).",
+            "  --field <descriptor>        # Only disassemble the given field (e.g.,"
+                + " Lcom/example/Class;->method()V).",
+            "  --method <descriptor>       # Only disassemble the given method (e.g.,"
+                + " Lcom/example/Class;->field:I).",
+            "  --version                   # Print the version of r8.",
+            "  --help                      # Print this message.");
 
     private final boolean allInfo;
     private final boolean useSmali;
diff --git a/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java b/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
index 115fcd8..2838917 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.internal.StringUtils;
 import com.android.tools.r8.utils.internal.collections.Pair;
-import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -104,12 +104,10 @@
   }
 
   static final String USAGE_MESSAGE =
-      String.join(
-          "\n",
-          ImmutableList.of(
-              "Usage: extractmarker [options] <input-files>",
-              " where <input-files> are D8 supported input/output files and options are:",
-              "  --help                  # Print this message."));
+      StringUtils.joinLines(
+          "Usage: extractmarker [options] <input-files>",
+          " where <input-files> are D8 supported input/output files and options are:",
+          "  --help                  # Print this message.");
 
   public static Builder builder() {
     return builder(new DiagnosticsHandler() {});
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index ceb96f7..f5926eb 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.utils.JoiningStringConsumer;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.internal.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -131,18 +132,18 @@
     }
   }
 
-  static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
-      "Usage: maindex [options] <input-files>",
-      " where <input-files> are JAR files",
-      " and options are:",
-      "  --lib <file>             # Add <file> as a library resource.",
-      "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
-      "                           # primary dex file.",
-      "  --main-dex-list <file>   # List of classes to place in the primary dex file.",
-      "  --main-dex-list-output <file>  # Output the full main-dex list in <file>.",
-      "  --version                # Print the version.",
-      "  --help                   # Print this message."));
-
+  static final String USAGE_MESSAGE =
+      StringUtils.joinLines(
+          "Usage: maindex [options] <input-files>",
+          " where <input-files> are JAR files",
+          " and options are:",
+          "  --lib <file>             # Add <file> as a library resource.",
+          "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
+          "                           # primary dex file.",
+          "  --main-dex-list <file>   # List of classes to place in the primary dex file.",
+          "  --main-dex-list-output <file>  # Output the full main-dex list in <file>.",
+          "  --version                # Print the version.",
+          "  --help                   # Print this message.");
 
   public static GenerateMainDexListCommand.Builder builder() {
     return new GenerateMainDexListCommand.Builder();
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
index 1bb711c..c331b84 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
@@ -46,7 +46,7 @@
         .build();
   }
 
-  static String getUsageMessage() {
+  public static String getUsageMessage() {
     StringBuilder builder = new StringBuilder();
     StringUtils.appendLines(builder, USAGE_MESSAGE);
     new ParseFlagPrinter().addFlags(getFlags()).appendLinesToBuilder(builder);
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
index 6ffb7b9..dd70705 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
@@ -37,10 +37,10 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.internal.Box;
 import com.android.tools.r8.utils.internal.FileUtils;
+import com.android.tools.r8.utils.internal.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -57,23 +57,20 @@
       ImmutableSet.of("--output", "--input", "--input-no-res", "--map", THREAD_COUNT_FLAG);
 
   static final String USAGE_MESSAGE =
-      String.join(
-          "\n",
-          Iterables.concat(
-              Arrays.asList(
-                  "The Relocator CLI is EXPERIMENTAL and is subject to change",
-                  "Usage: relocator [options]",
-                  " where options are:",
-                  "  --input <file>          # Input file to remap, class, zip or jar.",
-                  "  --input-no-res <file>   # Input file to remap, zip or jar.",
-                  "                          # Only .class file entries are included.",
-                  "  --output <file>         # Output result in <outfile>.",
-                  "  --map <from->to>        # Registers a mapping.",
-                  "  --map-diagnostics [:<type>] <from-level> <to-level>",
-                  "                          # Map diagnostics level.",
-                  "  --thread-count <number> # A specified number of threads to run with.",
-                  "  --version               # Print the version of d8.",
-                  "  --help                  # Print this message.")));
+      StringUtils.joinLines(
+          "The Relocator CLI is EXPERIMENTAL and is subject to change",
+          "Usage: relocator [options]",
+          " where options are:",
+          "  --input <file>          # Input file to remap, class, zip or jar.",
+          "  --input-no-res <file>   # Input file to remap, zip or jar.",
+          "                          # Only .class file entries are included.",
+          "  --output <file>         # Output result in <outfile>.",
+          "  --map <from->to>        # Registers a mapping.",
+          "  --map-diagnostics [:<type>] <from-level> <to-level>",
+          "                          # Map diagnostics level.",
+          "  --thread-count <number> # A specified number of threads to run with.",
+          "  --version               # Print the version of d8.",
+          "  --help                  # Print this message.");
 
   private final boolean printHelp;
   private final boolean printVersion;
diff --git a/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParserTest.java b/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParserTest.java
new file mode 100644
index 0000000..6dabf54
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParserTest.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2026, 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiDatabaseGeneratorCommandParserTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ApiDatabaseGeneratorCommandParserTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: apidatabasegenerator [options] <input-files>",
+            "where options are:",
+            "  --help",
+            "  -h                      # Print help.",
+            "  --version               # Print version.",
+            "  --output <database-file>",
+            "                          # Output result in <database-file> (must be a file, not a"
+                + " directory).",
+            "                          # Defaults to 'api_database.ser'."),
+        ApiDatabaseGeneratorCommandParser.getUsageMessage());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
index dc9c64e..ff16a70 100644
--- a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -6,14 +6,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.internal.StringUtils;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -138,6 +139,27 @@
   }
 
   @Test
+  public void testHelpMessage() {
+    assumeTrue(mode == Mode.NO_LIBRARY);
+    assertEquals(
+        StringUtils.lines(
+            "Usage: BackportedMethodList [options]",
+            " Options are:",
+            "",
+            "  --output <file>         # Output result in <file>.",
+            "  --min-api <number>      # Minimum Android API level for the application.",
+            "  --desugared-lib <file>  # Desugared library configuration (JSON from the"
+                + " configuration).",
+            "  --lib <file>            # The compilation SDK library (android.jar).",
+            "  --android-platform-build",
+            "                          # Compilation of platform code.",
+            "  --version               # Print the version of BackportedMethodList.",
+            "  --help",
+            "  -h                      # Print this message."),
+        BackportedMethodListCommand.usageMessage());
+  }
+
+  @Test
   public void testConsumer() throws Exception {
     for (int apiLevel = 1; apiLevel < AndroidApiLevel.LATEST.getLevel(); apiLevel++) {
       ListStringConsumer consumer = new ListStringConsumer();
@@ -172,7 +194,7 @@
 
   @Test
   public void testFullList() throws Exception {
-    Assume.assumeTrue(mode == Mode.NO_LIBRARY);
+    assumeTrue(mode == Mode.NO_LIBRARY);
     ListStringConsumer consumer = new ListStringConsumer();
     // Not setting neither min API level not library should produce the full list.
     BackportedMethodList.run(BackportedMethodListCommand.builder().setConsumer(consumer).build());
@@ -182,7 +204,7 @@
 
   @Test
   public void requireLibraryForDesugar() {
-    Assume.assumeTrue(mode == Mode.LIBRARY_DESUGAR || mode == Mode.LIBRARY_DESUGAR_11);
+    assumeTrue(mode == Mode.LIBRARY_DESUGAR || mode == Mode.LIBRARY_DESUGAR_11);
     // Require library when a desugar configuration is passed.
     try {
       BackportedMethodList.run(
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 4160500..caac031 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.internal.FileUtils;
+import com.android.tools.r8.utils.internal.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -167,6 +168,95 @@
   }
 
   @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: d8 [options] [@<argfile>] <input-files>",
+            " where <input-files> are any combination of dex, class, zip, jar, or apk files",
+            " and each <argfile> is a file containing additional arguments (one per line)",
+            " and options are:",
+            "  --debug                 # Compile with debugging information (default).",
+            "  --release               # Compile without debugging information.",
+            "  --output <file>         # Output result in <file>.",
+            "                          # <file> must be an existing directory or a zip file.",
+            "  --globals <file>        # Global synthetics <file> from a previous intermediate"
+                + " compilation.",
+            "                          # The <file> may be either a zip-archive of global"
+                + " synthetics or the",
+            "                          # global-synthetic files directly.",
+            "  --globals-output <file> # Output global synthetics in <file>.",
+            "                          # <file> must be an existing directory or a non-existent zip"
+                + " archive.",
+            "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
+            "  --classpath <file>      # Add <file> as a classpath resource.",
+            "  --min-api <number>      # Minimum Android API level compatibility (default: 1).",
+            "  --pg-map <file>         # Use <file> as a mapping file for distribution.",
+            "  --intermediate          # Compile an intermediate result intended for later",
+            "                          # merging.",
+            "  --file-per-class        # Produce a separate dex file per class.",
+            "                          # Synthetic classes are in their own file.",
+            "  --file-per-class-file   # Produce a separate dex file per input .class file.",
+            "                          # Synthetic classes are with their originating class.",
+            "  --no-desugaring         # Force disable desugaring.",
+            "  --desugared-lib <file>  # Specify desugared library configuration.",
+            "                          # <file> is a desugared library configuration (json).",
+            "  --main-dex-rules <file> # Proguard keep rules for classes to place in the",
+            "                          # primary dex file.",
+            "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
+            "  --main-dex-list-output <file>",
+            "                          # Output resulting main dex list in <file>.",
+            "  --force-enable-assertions[:[<class name>|<package name>...]]",
+            "  --force-ea[:[<class name>|<package name>...]]",
+            "                          # Forcefully enable javac generated assertion code.",
+            "  --force-disable-assertions[:[<class name>|<package name>...]]",
+            "  --force-da[:[<class name>|<package name>...]]",
+            "                          # Forcefully disable javac generated assertion code.",
+            "                          # This is the default handling of javac assertion code",
+            "                          # when generating DEX file format.",
+            "  --force-passthrough-assertions[:[<class name>|<package name>...]]",
+            "  --force-pa[:[<class name>|<package name>...]]",
+            "                          # Don't change javac generated assertion code. This",
+            "                          # is the default handling of javac assertion code when",
+            "                          # generating class file format.",
+            "  --force-assertions-handler:<handler method>[:[<class name>|<package name>...]]",
+            "  --force-ah:<handler method>[:[<class name>|<package name>...]]",
+            "                          # Change javac and kotlinc generated assertion code",
+            "                          # to invoke the method <handler method> with each",
+            "                          # assertion error instead of throwing it.",
+            "                          # The <handler method> is specified as a class name",
+            "                          # followed by a dot and the method name.",
+            "                          # The handler method must take a single argument of",
+            "                          # type java.lang.Throwable and have return type void.",
+            "  --thread-count <number> # Use <number> of threads for compilation.",
+            "                          # If not specified the number will be based on",
+            "                          # heuristics taking the number of cores into account.",
+            "  --map-diagnostics[:<type>] <from-level> <to-level>",
+            "                          # Map diagnostics of <type> (default any) reported as",
+            "                          # <from-level> to <to-level> where <from-level> and",
+            "                          # <to-level> are one of 'info', 'warning', or 'error'",
+            "                          # and the optional <type> is either the simple or",
+            "                          # fully qualified Java type name of a diagnostic.",
+            "                          # If <type> is unspecified, all diagnostics at ",
+            "                          # <from-level> will be mapped.",
+            "                          # Note that fatal compiler errors cannot be mapped.",
+            "  --android-platform-build",
+            "                          # Compile as a platform build where the"
+                + " runtime/bootclasspath",
+            "                          # is assumed to be the version specified by --min-api.",
+            "  --art-profile <input> <output>",
+            "                          # Rewrite human readable ART profile read from <input> and"
+                + " write to <output>.",
+            "  --startup-profile <file>",
+            "                          # Startup profile <file> to use for dex layout.",
+            "  --verbose-synthetic-names",
+            "                          # Enable verbose synthetic names that use the"
+                + " `$$ExternalSynthetic` marker.",
+            "  --version               # Print the version of d8.",
+            "  --help                  # Print this message."),
+        D8CommandParser.getUsageMessage());
+  }
+
+  @Test
   public void validOutputPath() throws Throwable {
     Path existingDir = temp.getRoot().toPath();
     Path nonExistingZip = existingDir.resolve("a-non-existing-archive.zip");
diff --git a/src/test/java/com/android/tools/r8/DexSegmentsTest.java b/src/test/java/com/android/tools/r8/DexSegmentsTest.java
new file mode 100644
index 0000000..9bcd6a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DexSegmentsTest.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2026, 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DexSegmentsTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public DexSegmentsTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.joinLines(
+            "Usage: dexsegments [options] <input-files>",
+            " where <input-files> are dex files",
+            "  --version               # Print the version of r8.",
+            "  --help                  # Print this message."),
+        DexSegments.Command.USAGE_MESSAGE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/DisassembleTest.java b/src/test/java/com/android/tools/r8/DisassembleTest.java
new file mode 100644
index 0000000..862d390
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DisassembleTest.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2026, 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DisassembleTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public DisassembleTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.joinLines(
+            "Usage: disasm [options] <input-files>",
+            " where <input-files> are dex files",
+            " and options are:",
+            "  --all                       # Include all information in disassembly.",
+            "  --smali                     # Disassemble using smali syntax.",
+            "  --ir                        # Print IR before and after optimization.",
+            "  --nocode                    # No printing of code objects.",
+            "  --pg-map <file>             # Proguard map <file> for mapping names.",
+            "  --pg-map-charset <charset>  # Charset for Proguard map file.",
+            "  --output                    # Specify a file or directory to write to.",
+            "  --class <descriptor>        # Only disassemble the given class (e.g.,"
+                + " Lcom/example/Class;).",
+            "  --field <descriptor>        # Only disassemble the given field (e.g.,"
+                + " Lcom/example/Class;->method()V).",
+            "  --method <descriptor>       # Only disassemble the given method (e.g.,"
+                + " Lcom/example/Class;->field:I).",
+            "  --version                   # Print the version of r8.",
+            "  --help                      # Print this message."),
+        Disassemble.DisassembleCommand.USAGE_MESSAGE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ExtractKeepAnnoRulesCommandTest.java b/src/test/java/com/android/tools/r8/ExtractKeepAnnoRulesCommandTest.java
new file mode 100644
index 0000000..45ad42c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ExtractKeepAnnoRulesCommandTest.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2026, 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ExtractKeepAnnoRulesCommandTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ExtractKeepAnnoRulesCommandTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: EXPERIMENTAL tool to extract keep rules from keep annotations",
+            "  --rules-output <file>      # Output the extracted keep rules.",
+            "  --rules-target r8|pg       # Optimizer rules are for (default r8).",
+            "  --version                  # Print the version.",
+            "  --help                     # Print this message."),
+        ExtractKeepAnnoRulesCommand.USAGE_MESSAGE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 0d5c8f4..1826b7e 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -43,6 +43,17 @@
         getTestParameters().withAllRuntimes().withAllApiLevels().build(), BooleanUtils.values());
   }
 
+  @Test
+  public void testHelpMessage() {
+    parameters.assumeIsOrSimulateNoneRuntime();
+    assertEquals(
+        StringUtils.joinLines(
+            "Usage: extractmarker [options] <input-files>",
+            " where <input-files> are D8 supported input/output files and options are:",
+            "  --help                  # Print this message."),
+        ExtractMarkerCommand.USAGE_MESSAGE);
+  }
+
   public ExtractMarkerTest(TestParameters parameters, boolean includeClassesChecksum) {
     this.parameters = parameters;
     this.includeClassesChecksum = includeClassesChecksum;
diff --git a/src/test/java/com/android/tools/r8/ExtractR8RulesCommandTest.java b/src/test/java/com/android/tools/r8/ExtractR8RulesCommandTest.java
new file mode 100644
index 0000000..4175e2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ExtractR8RulesCommandTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2026, 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ExtractR8RulesCommandTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ExtractR8RulesCommandTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: TBD",
+            "  --rules-output <file>      # Output the extracted keep rules.",
+            "  --compiler-version <version>  # Output the proguard rules extracted.",
+            "  --include-origin-comments  # Include comments with origin for extracted rules.",
+            "  --version                  # Print the version.",
+            "  --help                     # Print this message."),
+        ExtractR8RulesCommand.USAGE_MESSAGE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListCommandParserTest.java b/src/test/java/com/android/tools/r8/GenerateMainDexListCommandParserTest.java
new file mode 100644
index 0000000..7c9d4ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListCommandParserTest.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2026, 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenerateMainDexListCommandParserTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public GenerateMainDexListCommandParserTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.joinLines(
+            "Usage: maindex [options] <input-files>",
+            " where <input-files> are JAR files",
+            " and options are:",
+            "  --lib <file>             # Add <file> as a library resource.",
+            "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
+            "                           # primary dex file.",
+            "  --main-dex-list <file>   # List of classes to place in the primary dex file.",
+            "  --main-dex-list-output <file>  # Output the full main-dex list in <file>.",
+            "  --version                # Print the version.",
+            "  --help                   # Print this message."),
+        GenerateMainDexListCommand.USAGE_MESSAGE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParserTest.java b/src/test/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParserTest.java
new file mode 100644
index 0000000..0084fa0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParserTest.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2026, 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GlobalSyntheticsGeneratorCommandParserTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public GlobalSyntheticsGeneratorCommandParserTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: globalsyntheticsgenerator [options] where options are:",
+            "",
+            "  --min-api <number>      # Minimum Android API level compatibility (default: 1).",
+            "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
+            "  --output <globals-file> # Output result in <globals-file>.",
+            "  --classfile             # Generate globals for only classfile to classfile"
+                + " desugaring.",
+            "                          # (By default globals for both classfile and dex desugaring"
+                + " are generated).",
+            "  --verbose-synthetic-names",
+            "                          # Enable verbose synthetic names that use the"
+                + " `$$ExternalSynthetic` marker.",
+            "  --version               # Print the version of globalsyntheticsgenerator.",
+            "  --help                  # Print this message."),
+        GlobalSyntheticsGeneratorCommandParser.getUsageMessage());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 593c434..ff59328 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.internal.FileUtils;
+import com.android.tools.r8.utils.internal.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -84,6 +85,68 @@
             .build());
   }
 
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: l8 [options] <input-files>",
+            " where <input-files> are any combination class, zip, or jar files",
+            " where <input-files> are any combination of dex, class, zip, jar, or apk files",
+            " and options are:",
+            "  --debug                 # Compile with debugging information (default).",
+            "  --release               # Compile without debugging information.",
+            "  --output <file>         # Output result in <file>.",
+            "                          # <file> must be an existing directory or a zip file.",
+            "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
+            "  --min-api <number>      # Minimum Android API level compatibility (default: 1).",
+            "  --pg-conf <file>        # Proguard configuration <file>.",
+            "  --pg-map-output <file>  # Output the resulting name and line mapping to <file>.",
+            "  --partition-map-output <file>",
+            "                          # Output the resulting mapping to <file>.",
+            "  --desugared-lib <file>  # Specify desugared library configuration.",
+            "                          # <file> is a desugared library configuration (json).",
+            "  --force-enable-assertions[:[<class name>|<package name>...]]",
+            "  --force-ea[:[<class name>|<package name>...]]",
+            "                          # Forcefully enable javac generated assertion code.",
+            "  --force-disable-assertions[:[<class name>|<package name>...]]",
+            "  --force-da[:[<class name>|<package name>...]]",
+            "                          # Forcefully disable javac generated assertion code.",
+            "                          # This is the default handling of javac assertion code",
+            "                          # when generating DEX file format.",
+            "  --force-passthrough-assertions[:[<class name>|<package name>...]]",
+            "  --force-pa[:[<class name>|<package name>...]]",
+            "                          # Don't change javac generated assertion code. This",
+            "                          # is the default handling of javac assertion code when",
+            "                          # generating class file format.",
+            "  --force-assertions-handler:<handler method>[:[<class name>|<package name>...]]",
+            "  --force-ah:<handler method>[:[<class name>|<package name>...]]",
+            "                          # Change javac and kotlinc generated assertion code",
+            "                          # to invoke the method <handler method> with each",
+            "                          # assertion error instead of throwing it.",
+            "                          # The <handler method> is specified as a class name",
+            "                          # followed by a dot and the method name.",
+            "                          # The handler method must take a single argument of",
+            "                          # type java.lang.Throwable and have return type void.",
+            "  --thread-count <number> # Use <number> of threads for compilation.",
+            "                          # If not specified the number will be based on",
+            "                          # heuristics taking the number of cores into account.",
+            "  --map-diagnostics[:<type>] <from-level> <to-level>",
+            "                          # Map diagnostics of <type> (default any) reported as",
+            "                          # <from-level> to <to-level> where <from-level> and",
+            "                          # <to-level> are one of 'info', 'warning', or 'error'",
+            "                          # and the optional <type> is either the simple or",
+            "                          # fully qualified Java type name of a diagnostic.",
+            "                          # If <type> is unspecified, all diagnostics at ",
+            "                          # <from-level> will be mapped.",
+            "                          # Note that fatal compiler errors cannot be mapped.",
+            "  --art-profile <input> <output>",
+            "                          # Rewrite human readable ART profile read from <input> and"
+                + " write to <output>.",
+            "  --version               # Print the version of l8.",
+            "  --help                  # Print this message."),
+        L8CommandParser.getUsageMessage());
+  }
+
   private void verifyEmptyCommand(L8Command command) {
     BaseCompilerCommand compilationCommand =
         command.getD8Command() == null ? command.getR8Command() : command.getD8Command();
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index a81cdab..79ab660 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.internal.FileUtils;
+import com.android.tools.r8.utils.internal.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.io.IOException;
@@ -356,6 +357,118 @@
   }
 
   @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: r8 [options] [@<argfile>] <input-files>",
+            " where <input-files> are any combination class, zip, or jar files",
+            " and each <argfile> is a file containing additional arguments (one per line)",
+            " and options are:",
+            "  --release               # Compile without debugging information (default).",
+            "  --debug                 # Compile with debugging information.",
+            "  --dex                   # Compile program to DEX file format (default).",
+            "  --classfile             # Compile program to Java classfile format.",
+            "  --output <file>         # Output result in <file>.",
+            "                          # <file> must be an existing directory or a zip file.",
+            "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
+            "  --classpath <file>      # Add <file> as a classpath resource.",
+            "  --min-api <number>      # Minimum Android API level compatibility (default: 1).",
+            "  --pg-compat             # Compile with R8 in Proguard compatibility mode.",
+            "  --pg-conf <file>        # Proguard configuration <file>.",
+            "  --pg-conf-output <file> # Output the collective configuration to <file>.",
+            "  --pg-map <file>         # Use <file> as a mapping file for distribution and"
+                + " composition with output mapping file.",
+            "  --pg-map-output <file>  # Output the resulting name and line mapping to <file>.",
+            "  --partition-map-output <file>",
+            "                          # Output the resulting mapping to <file>.",
+            "  --desugared-lib <file>  # Specify desugared library configuration.",
+            "                          # <file> is a desugared library configuration (json).",
+            "  --no-tree-shaking       # Force disable tree shaking of unreachable classes.",
+            "  --no-minification       # Force disable minification of names.",
+            "  --no-data-resources     # Ignore all data resources.",
+            "  --no-desugaring         # Force disable desugaring.",
+            "  --main-dex-rules <file> # Proguard keep rules for classes to place in the",
+            "                          # primary dex file.",
+            "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
+            "  --android-resources <input> <output>",
+            "                          # Add android resource input and output to be used in"
+                + " resource shrinking. Both ",
+            "                          # input and output must be specified.",
+            "  --android-resources-usage-log <file>",
+            "                          # Write the resource shrinking usage log to <file>.",
+            "  --feature <input>[:|;<res-input>] <output>[:|;<res-output>]",
+            "                          # Add feature <input> file to <output> file. Several ",
+            "                          # occurrences can map to the same output. If <res-input> and"
+                + " <res-output> are ",
+            "                          # specified use these as resource shrinker input and output."
+                + " Separator is : on ",
+            "                          # linux/mac, ; on windows. It is possible to supply resource"
+                + " only features by ",
+            "                          #  using an empty string for <input> and <output>, e.g."
+                + " --feature :in.ap_ :out.ap_",
+            "  --isolated-splits       # Specifies that the application is using isolated splits,"
+                + " i.e., if split APKs installed for this application are loaded into their own"
+                + " Context objects.",
+            "  --main-dex-list-output <file>",
+            "                          # Output the full main-dex list in <file>.",
+            "  --force-enable-assertions[:[<class name>|<package name>...]]",
+            "  --force-ea[:[<class name>|<package name>...]]",
+            "                          # Forcefully enable javac generated assertion code.",
+            "  --force-disable-assertions[:[<class name>|<package name>...]]",
+            "  --force-da[:[<class name>|<package name>...]]",
+            "                          # Forcefully disable javac generated assertion code.",
+            "                          # This is the default handling of javac assertion code",
+            "                          # when generating DEX file format.",
+            "  --force-passthrough-assertions[:[<class name>|<package name>...]]",
+            "  --force-pa[:[<class name>|<package name>...]]",
+            "                          # Don't change javac generated assertion code. This",
+            "                          # is the default handling of javac assertion code when",
+            "                          # generating class file format.",
+            "  --force-assertions-handler:<handler method>[:[<class name>|<package name>...]]",
+            "  --force-ah:<handler method>[:[<class name>|<package name>...]]",
+            "                          # Change javac and kotlinc generated assertion code",
+            "                          # to invoke the method <handler method> with each",
+            "                          # assertion error instead of throwing it.",
+            "                          # The <handler method> is specified as a class name",
+            "                          # followed by a dot and the method name.",
+            "                          # The handler method must take a single argument of",
+            "                          # type java.lang.Throwable and have return type void.",
+            "  --thread-count <number> # Use <number> of threads for compilation.",
+            "                          # If not specified the number will be based on",
+            "                          # heuristics taking the number of cores into account.",
+            "  --map-diagnostics[:<type>] <from-level> <to-level>",
+            "                          # Map diagnostics of <type> (default any) reported as",
+            "                          # <from-level> to <to-level> where <from-level> and",
+            "                          # <to-level> are one of 'info', 'warning', or 'error'",
+            "                          # and the optional <type> is either the simple or",
+            "                          # fully qualified Java type name of a diagnostic.",
+            "                          # If <type> is unspecified, all diagnostics at ",
+            "                          # <from-level> will be mapped.",
+            "                          # Note that fatal compiler errors cannot be mapped.",
+            "  --map-id-template <template>",
+            "                          # Set the map-id to <template>.",
+            "                          # The <template> can reference the variables:",
+            "                          #   %MAP_HASH: compiler generated mapping hash.",
+            "  --source-file-template <template>",
+            "                          # Set all source-file attributes to <template>",
+            "                          # The <template> can reference the variables:",
+            "                          #   %MAP_ID: map id (e.g., value of --map-id-template).",
+            "                          #   %MAP_HASH: compiler generated mapping hash.",
+            "  --android-platform-build",
+            "                          # Compile as a platform build where the"
+                + " runtime/bootclasspath",
+            "                          # is assumed to be the version specified by --min-api.",
+            "  --art-profile <input> <output>",
+            "                          # Rewrite human readable ART profile read from <input> and"
+                + " write to <output>.",
+            "  --startup-profile <file>",
+            "                          # Startup profile <file> to use for dex layout.",
+            "  --version               # Print the version of r8.",
+            "  --help                  # Print this message."),
+        R8CommandParser.getUsageMessage());
+  }
+
+  @Test
   public void validOutputPath() throws Throwable {
     Path existingDir = temp.getRoot().toPath();
     Path nonExistingZip = existingDir.resolve("a-non-existing-archive.zip");
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index cc872e6..11daf47 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -18,8 +18,11 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.internal.StringUtils;
 import com.android.tools.r8.utils.timing.Timing;
 import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
 import java.nio.file.Path;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -36,6 +39,31 @@
     return TestParameters.builder().withNoneRuntime().build();
   }
 
+  @Test
+  public void testHelpMessage() throws Exception {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    PrintStream originalOut = System.out;
+    System.setOut(new PrintStream(out));
+    try {
+      BisectOptions.printHelp();
+    } finally {
+      System.setOut(originalOut);
+    }
+    assertEquals(
+        StringUtils.lines(
+            "--bad <apk>       Known bad APK.",
+            "--command <file>  Command to run after each bisection.",
+            "--good <apk>      Known good APK.",
+            "--help",
+            "--output <dir>    Output directory.",
+            "--result-bad      Bisect again assuming previous run was",
+            "        bad.",
+            "--result-good     Bisect again assuming previous run was",
+            "        good.",
+            "--state <file>    Bisection state."),
+        out.toString());
+  }
+
   public BisectTest(TestParameters parameters) {
     parameters.assertNoneRuntime();
   }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
index abf86b0..4f03ca0 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.compatproguard.CompatProguard.CompatProguardOptions;
+import com.android.tools.r8.utils.internal.StringUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,6 +38,38 @@
   }
 
   @Test
+  public void testHelpMessage() throws Exception {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    PrintStream originalOut = System.out;
+    System.setOut(new PrintStream(out));
+    try {
+      CompatProguard.main(new String[] {"--help"});
+    } finally {
+      System.setOut(originalOut);
+    }
+    assertEquals(
+        StringUtils.lines(
+            "",
+            "CompatProguard main (build engineering)",
+            "",
+            "compatproguard [options] --output <dir> <proguard-config>*",
+            "",
+            "Where options are:",
+            "-h/--help            : print this help message",
+            "--release            : compile without debugging information (default).",
+            "--debug              : compile with debugging information.",
+            "--min-api n          : specify the targeted min android api level",
+            "--main-dex-list list : specify main dex list for multi-dexing",
+            "--minimal-main-dex   : ignored (provided for compatibility)",
+            "--multi-dex          : ignored (provided for compatibility)",
+            "--no-locals          : ignored (provided for compatibility)",
+            "--core-library       : ignored (provided for compatibility)",
+            "--force-proguard-compatibility : Proguard compatibility mode",
+            "--no-data-resources  : ignore all data resources"),
+        out.toString());
+  }
+
+  @Test
   public void testDefaultDataResources() {
     CompatProguardOptions options = parseArgs();
     assertNull(options.output);
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsListCommandTest.java b/src/test/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsListCommandTest.java
new file mode 100644
index 0000000..03432b4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsListCommandTest.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2026, 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.ir.desugar.desugaredlibrary.lint;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DesugaredMethodsListCommandTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public DesugaredMethodsListCommandTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: desugaredmethods [options] where  options are:",
+            "  --output <file>         # Output result in <file>.",
+            "                          # <file> must be an existing directory or a zip file.",
+            "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
+            "  --min-api <number>      # Minimum Android API level compatibility (default: 1).",
+            "  --version               # Print the version of DesugaredMethods.",
+            "  --help                  # Print this message.",
+            "  --desugared-lib <file>  # Specify desugared library configuration.",
+            "                          # <file> is a desugared library configuration (json).",
+            "  --android-platform-build",
+            "                          # Compile as a platform build where the"
+                + " runtime/bootclasspath",
+            "                          # is assumed to be the version specified by --min-api.",
+            "  --desugared-lib-jar <file>",
+            "                          # Specify desugared library jar."),
+        DesugaredMethodsListCommand.getUsageMessage());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorCommandTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorCommandTest.java
index 6766ee7..6c46829 100644
--- a/src/test/java/com/android/tools/r8/relocator/RelocatorCommandTest.java
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorCommandTest.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.references.PackageReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.internal.StringUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.List;
@@ -46,6 +47,26 @@
   }
 
   @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.joinLines(
+            "The Relocator CLI is EXPERIMENTAL and is subject to change",
+            "Usage: relocator [options]",
+            " where options are:",
+            "  --input <file>          # Input file to remap, class, zip or jar.",
+            "  --input-no-res <file>   # Input file to remap, zip or jar.",
+            "                          # Only .class file entries are included.",
+            "  --output <file>         # Output result in <outfile>.",
+            "  --map <from->to>        # Registers a mapping.",
+            "  --map-diagnostics [:<type>] <from-level> <to-level>",
+            "                          # Map diagnostics level.",
+            "  --thread-count <number> # A specified number of threads to run with.",
+            "  --version               # Print the version of d8.",
+            "  --help                  # Print this message."),
+        RelocatorCommand.USAGE_MESSAGE);
+  }
+
+  @Test
   public void testCommandBuilder() throws CompilationFailedException, IOException {
     Path input1 = temp.newFile("in1.jar").toPath();
     Path input2 = temp.newFile("in2.jar").toPath();
diff --git a/src/test/java/com/android/tools/r8/retrace/PartitionCommandParserTest.java b/src/test/java/com/android/tools/r8/retrace/PartitionCommandParserTest.java
new file mode 100644
index 0000000..de95ad3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/PartitionCommandParserTest.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2026, 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.retrace;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.internal.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartitionCommandParserTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public PartitionCommandParserTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: partition [options] <proguard-map>",
+            " where <proguard-map> is a generated mapping file and options are:",
+            "",
+            "  --output <partition-map>",
+            "                          # Output destination of partitioned map.",
+            "  --version               # Print the version.",
+            "  --help",
+            "  -h                      # Print this message."),
+        PartitionCommandParser.getUsageMessage());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 60d2e23..2fe5bb5 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -194,6 +194,27 @@
   }
 
   @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: retrace [options] <proguard-map> [stack-trace-file] where <proguard-map> is a"
+                + " generated mapping file and options are:",
+            "  --regex <regexp>",
+            "  --r <regexp>            # Regular expression for parsing stack-trace-file as lines",
+            "  --verbose               # Get verbose retraced output",
+            "  --info                  # Write information messages to stdout",
+            "  --quiet                 # Silence ordinary messages printed to stdout",
+            "  --verify-mapping-file-hash",
+            "                          # Verify the mapping file hash",
+            "  --partition-map <file>",
+            "  --p <file>              # Partition map to use",
+            "  --help",
+            "  -h                      # Print this message.",
+            "  --version               # Print the version."),
+        Retrace.getUsageMessage());
+  }
+
+  @Test
   public void testVersion() throws Exception {
     ProcessResult processResult = runRetraceCommandLine(null, Arrays.asList("--version"));
     assertEquals(0, processResult.exitCode);
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index 5501710..9dd6eef 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -66,6 +66,44 @@
     TraceReferencesCommand.builder().build();
   }
 
+  @Test
+  public void testHelpMessage() {
+    assertEquals(
+        StringUtils.lines(
+            "Usage: tracereferences <command> [<options>] [@<argfile>]",
+            " Where <command> is one of:",
+            "  --check                 # Run emitting only diagnostics messages.",
+            "  --keep-rules [<keep-rules-options>]",
+            "                          # Traced references will be output in the keep-rules",
+            "                          # format.",
+            "  --natives               # EXPERIMENTAL.",
+            " and each <argfile> is a file containing additional options (one per line)",
+            " and options are:",
+            "  --lib <file|jdk-home>   # Add <file|jdk-home> runtime library.",
+            "  --source <file>         # Add <file> as a source for tracing references.",
+            "  --target <file>         # Add <file> as a target for tracing references. When",
+            "                          # target is not specified all references from source",
+            "                          # outside of library are treated as a missing",
+            "                          # references.",
+            "  --output <file>         # Output result in <outfile>. If not passed the",
+            "                          # result will go to standard out.",
+            "  --map-diagnostics[:<type>] <from-level> <to-level>",
+            "                          # Map diagnostics of <type> (default any) reported as",
+            "                          # <from-level> to <to-level> where <from-level> and",
+            "                          # <to-level> are one of 'info', 'warning', or 'error'",
+            "                          # and the optional <type> is either the simple or",
+            "                          # fully qualified Java type name of a diagnostic.",
+            "                          # If <type> is unspecified, all diagnostics at ",
+            "                          # <from-level> will be mapped.",
+            "                          # Note that fatal compiler errors cannot be mapped.",
+            "  --version               # Print the version of tracereferences.",
+            "  --help                  # Print this message.",
+            " and <keep-rule-options> are:",
+            "  --allowobfuscation      # Output keep rules with the allowobfuscation",
+            "                          # modifier (defaults to rules without the modifier)"),
+        TraceReferencesCommandParser.getUsageMessage());
+  }
+
   @Test(expected = CompilationFailedException.class)
   public void emptyRun() throws Throwable {
     DiagnosticsChecker.checkErrorsContains(