[llvm] 115eae2 - [FileCheck] Improve colors in input dumps (#204936)

via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 27 16:39:20 PDT 2026


Author: Joel E. Denny
Date: 2026-06-27T23:39:14Z
New Revision: 115eae28058f0d322f1d7d3eab0ab8c13e95e463

URL: https://github.com/llvm/llvm-project/commit/115eae28058f0d322f1d7d3eab0ab8c13e95e463
DIFF: https://github.com/llvm/llvm-project/commit/115eae28058f0d322f1d7d3eab0ab8c13e95e463.diff

LOG: [FileCheck] Improve colors in input dumps (#204936)

This patch makes two improvements to colors used in FileCheck input
dumps:

1. Without this patch, input line numbers and ellipses have a foreground
    color of black, which is hard to see in a terminal with a dark color
    theme. This patch changes that to bright black (a grayish color), which
    looks good to me for all terminal themes I have tried while remaining
    distinct from the input text.
2. Without this patch, the input text is accidentally set to bold when
    neither `-v` or `-vv` is specified. Perhaps I never noticed because I
    tend to always use `-vv`. This patch changes that to use the terminal's
    default color.

Case 2 exposes a problem with LLVM's color implementation. Without this
patch, the call to `WithColor`'s constructor actually specifies bold as
`false`, but `WithColor` ignores that when the color is `SAVEDCOLOR`.
While it seems like that should be fixed, I am concerned about the
impact of such a fix on other tools that might have come to
inadvertently depend on the old behavior. For now, this patch adds fixme
comments to the color APIs.

To facilitate testing, this patch adds a lit substitution with a sed
script that translates ANSI escape sequences into plain text format with
symbolic names. It also updates an existing FileCheck test to use it.

Added: 
    llvm/test/FileCheck/dump-input/color.txt

Modified: 
    llvm/include/llvm/Support/WithColor.h
    llvm/include/llvm/Support/raw_ostream.h
    llvm/test/FileCheck/opt-color.txt
    llvm/test/lit.cfg.py
    llvm/utils/FileCheck/FileCheck.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Support/WithColor.h b/llvm/include/llvm/Support/WithColor.h
index 2835e179e6195..febf211c3a391 100644
--- a/llvm/include/llvm/Support/WithColor.h
+++ b/llvm/include/llvm/Support/WithColor.h
@@ -68,6 +68,8 @@ class WithColor {
   /// @param Bold Bold/brighter text, default false
   /// @param BG If true, change the background, default: change foreground
   /// @param Mode Enable, disable or compute whether to use colors.
+  ///
+  /// FIXME: If Color == SAVEDCOLOR, Bold == false is currently ignored.
   LLVM_CTOR_NODISCARD WithColor(
       raw_ostream &OS, raw_ostream::Colors Color = raw_ostream::SAVEDCOLOR,
       bool Bold = false, bool BG = false, ColorMode Mode = ColorMode::Auto)
@@ -117,6 +119,8 @@ class WithColor {
   /// change only the bold attribute, and keep colors untouched
   /// @param Bold Bold/brighter text, default false
   /// @param BG If true, change the background, default: change foreground
+  ///
+  /// FIXME: If Color == SAVEDCOLOR, Bold == false is currently ignored.
   LLVM_ABI WithColor &changeColor(raw_ostream::Colors Color, bool Bold = false,
                                   bool BG = false);
 

diff  --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index 70916d8e4adb0..1e66052c849b7 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -332,6 +332,8 @@ class LLVM_ABI raw_ostream {
   /// @param Bold bold/brighter text, default false
   /// @param BG if true change the background, default: change foreground
   /// @returns itself so it can be used within << invocations
+  ///
+  /// FIXME: If Color == SAVEDCOLOR, Bold == false is currently ignored.
   virtual raw_ostream &changeColor(enum Colors Color, bool Bold = false,
                                    bool BG = false);
 

diff  --git a/llvm/test/FileCheck/dump-input/color.txt b/llvm/test/FileCheck/dump-input/color.txt
new file mode 100644
index 0000000000000..9d343c0e0fb08
--- /dev/null
+++ b/llvm/test/FileCheck/dump-input/color.txt
@@ -0,0 +1,112 @@
+; Check that, when color is enabled, input dumps are colored as desired.  Other
+; tests check when colors are enabled generally.
+;
+; There are many redundant ANSI escape sequences in FileCheck's color output.
+; In particular, all sequences immediately preceding a <reset> with no text in
+; between are redundant.  These sequences should have no impact on what the
+; FileCheck user sees.
+
+; REQUIRES: ansi-escapes
+
+; RUN: split-file %s %t
+
+; DEFINE: %{opts} =
+; DEFINE: %{run} = \
+; DEFINE:   %ProtectFileCheckOutput \
+; DEFINE:   not FileCheck -color -dump-input=always -dump-input-filter=error \
+; DEFINE:       -dump-input-context=2 %{opts} %t/check.txt < %t/input.txt \
+; DEFINE:       2>&1 | \
+; DEFINE:     %{reveal-ansi-escapes} | \
+; DEFINE:     FileCheck %s -match-full-lines -check-prefixes
+
+; REDEFINE: %{opts} =
+; RUN: %{run} CHECK-Q
+; REDEFINE: %{opts} = -v
+; RUN: %{run} CHECK-V
+; REDEFINE: %{opts} = -vv
+; RUN: %{run} CHECK-VV
+
+; Without -v or -vv:
+; - Line numbers and ellipses are bright black.
+; - Input text is reset to the default.
+; - Errors are red.
+; - Fuzzy matches are magenta.
+;
+;      CHECK-Q: Input was:
+; CHECK-Q-NEXT: <<<<<<
+; CHECK-Q-NEXT: <bold><reset><bold-bright-black> 1: <reset><bold><reset>hello <reset>
+; CHECK-Q-NEXT: <bold-red>dag:2'0                                            {          search range start (exclusive)
+; CHECK-Q-NEXT: <reset><bold-red>dag:2'1                                                error: no match found in search range
+; CHECK-Q-NEXT: <reset><bold-bright-black>       2: <reset><bold><reset>jello <reset>
+; CHECK-Q-NEXT: <bold-magenta>dag:2'2                                   ?               possible intended match
+; CHECK-Q-NEXT: <reset><bold-bright-black>       3: <reset><bold><reset>end <reset>
+; CHECK-Q-NEXT: <bold-red>dag:2'3                                           }           search range end (exclusive)
+; CHECK-Q-NEXT: <reset><bold-bright-black>       4: <reset><bold><reset>foo <reset>
+; CHECK-Q-NEXT: <bold-bright-black>              5: <reset><bold><reset>foo <reset>
+; CHECK-Q-NEXT: <bold-bright-black>              .<reset>
+; CHECK-Q-NEXT: <bold-bright-black>              .<reset>
+; CHECK-Q-NEXT: <bold-bright-black>              .<reset>
+; CHECK-Q-NEXT: >>>>>>
+
+; With -v, all of the above is true but:
+; - Successes are green.
+; - Unmatched input text now has a cyan background.
+;
+;      CHECK-V: Input was:
+; CHECK-V-NEXT: <<<<<<
+; CHECK-V-NEXT: <bold><reset><bold-bright-black>  1: <reset><bold><bg-bold-cyan><reset>hello<bg-bold-cyan> <reset>
+; CHECK-V-NEXT: <bold-green>dag:1                                                      ^~~~~
+; CHECK-V-NEXT: <reset><bold-red>dag:2'0                                                                  {        search range start (exclusive)
+; CHECK-V-NEXT: <reset><bold-red>dag:2'1                                                                           error: no match found in search range
+; CHECK-V-NEXT: <reset><bold-bright-black>        2: <reset><bold><bg-bold-cyan>jello <reset>
+; CHECK-V-NEXT: <bold-magenta>dag:2'2                                           ?                                  possible intended match
+; CHECK-V-NEXT: <reset><bold-bright-black>        3: <reset><bold><bg-bold-cyan><reset>end<bg-bold-cyan> <reset>
+; CHECK-V-NEXT: <bold-green>label:3                                                    ^~~
+; CHECK-V-NEXT: <reset><bold-red>dag:2'3                                                                }          search range end (exclusive)
+; CHECK-V-NEXT: <reset><bold-bright-black>        4: <reset><bold><bg-bold-cyan>foo <reset>
+; CHECK-V-NEXT: <bold-bright-black>               5: <reset><bold><bg-bold-cyan>foo <reset>
+; CHECK-V-NEXT: <bold-bright-black>               .<reset>
+; CHECK-V-NEXT: <bold-bright-black>               .<reset>
+; CHECK-V-NEXT: <bold-bright-black>               .<reset>
+; CHECK-V-NEXT: >>>>>>
+
+; With -vv, all of the above is true but:
+; - Discarded matches are cyan.
+;
+;      CHECK-VV: Input was:
+; CHECK-VV-NEXT: <<<<<<
+; CHECK-VV-NEXT: <bold><reset><bold-bright-black>  1: <reset><bold><bg-bold-cyan><reset>hello<bg-bold-cyan> <reset>
+; CHECK-VV-NEXT: <bold-green>dag:1                                                      ^~~~~
+; CHECK-VV-NEXT: <reset><bold-cyan>dag:2'0                                              !~~~~                       discard: overlaps earlier match
+; CHECK-VV-NEXT: <reset><bold-red>dag:2'1                                                                  {        search range start (exclusive)
+; CHECK-VV-NEXT: <reset><bold-red>dag:2'2                                                                           error: no match found in search range
+; CHECK-VV-NEXT: <reset><bold-bright-black>        2: <reset><bold><bg-bold-cyan>jello <reset>
+; CHECK-VV-NEXT: <bold-magenta>dag:2'3                                           ?                                  possible intended match
+; CHECK-VV-NEXT: <reset><bold-bright-black>        3: <reset><bold><bg-bold-cyan><reset>end<bg-bold-cyan> <reset>
+; CHECK-VV-NEXT: <bold-green>label:3                                                    ^~~
+; CHECK-VV-NEXT: <reset><bold-red>dag:2'4                                                                }          search range end (exclusive)
+; CHECK-VV-NEXT: <reset><bold-bright-black>        4: <reset><bold><bg-bold-cyan>foo <reset>
+; CHECK-VV-NEXT: <bold-bright-black>               5: <reset><bold><bg-bold-cyan>foo <reset>
+; CHECK-VV-NEXT: <bold-bright-black>               .<reset>
+; CHECK-VV-NEXT: <bold-bright-black>               .<reset>
+; CHECK-VV-NEXT: <bold-bright-black>               .<reset>
+; CHECK-VV-NEXT: >>>>>>
+
+;--- check.txt
+CHECK-DAG: hello
+CHECK-DAG: hello
+CHECK-LABEL: end
+
+;--- input.txt
+hello
+jello
+end
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+foo

diff  --git a/llvm/test/FileCheck/opt-color.txt b/llvm/test/FileCheck/opt-color.txt
index b12fa768881fe..87be3f899f967 100644
--- a/llvm/test/FileCheck/opt-color.txt
+++ b/llvm/test/FileCheck/opt-color.txt
@@ -1,22 +1,30 @@
+; Check when color is enabled.
+
+; REQUIRES: ansi-escapes
+
 ; Create a case that produces a simple diagnostic.
 ; RUN: echo foo > %t.in
 ; CHECK: bar
 
 ; Run without and with -color.  In the former case, FileCheck should suppress
 ; color in its diagnostics because stderr is a file.
-; RUN: %ProtectFileCheckOutput not FileCheck %s < %t.in 2> %t.no-color
-; RUN: %ProtectFileCheckOutput not FileCheck -color %s < %t.in 2> %t.color
+; RUN: %ProtectFileCheckOutput not FileCheck %s < %t.in 2>&1 | \
+; RUN:   %{reveal-ansi-escapes} > %t.no-color
+; RUN: %ProtectFileCheckOutput not FileCheck -color %s < %t.in 2>&1 | \
+; RUN:   %{reveal-ansi-escapes} > %t.color
 
 ; Check whether color was produced.
-; RUN: FileCheck -check-prefix NO-COLOR %s < %t.no-color
-; RUN: FileCheck -check-prefix COLOR %s < %t.color
+; DEFINE: %{no-color} = FileCheck -check-prefix=NO-COLOR %s
+; DEFINE: %{color} = FileCheck -check-prefix=COLOR %s
+; RUN: %{no-color} < %t.no-color
+; RUN: %{color} < %t.color
 
 ; Make sure our NO-COLOR and COLOR patterns are sane: they don't match the
 ; opposite cases.
-; RUN: not FileCheck -check-prefix COLOR %s < %t.no-color
-; RUN: not FileCheck -check-prefix NO-COLOR %s < %t.color
+; RUN: not %{no-color} < %t.color
+; RUN: not %{color} < %t.no-color
 
-; I don't know of a good way to check for ANSI color codes, so just make sure
-; some new characters show up where those codes should appear.
+; The goal here is not to check the exact details of how colors are used.  We
+; just check whether colors are enabled at all.
 ; NO-COLOR: : error: CHECK: expected string not found in input
-; COLOR: : {{.+}}error: {{.+}}CHECK: expected string not found in input
+; COLOR: <bold-red>error:

diff  --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py
index 09df1e3fd6281..cd028963dd59e 100644
--- a/llvm/test/lit.cfg.py
+++ b/llvm/test/lit.cfg.py
@@ -853,6 +853,99 @@ def host_unwind_supports_jit():
 if config.has_logf128:
     config.available_features.add("has_logf128")
 
+# ANSI escape sequences.
+#
+# For example:
+#
+#   REQUIRES: ansi-escapes
+#   RUN: some-cmd -color 2>&1 | %{reveal-ansi-escapes} | FileCheck %s
+#   CHECK: Once in a <blue>blue moon<reset>, I'm <green>green with envy<reset>.
+if platform.system() not in ["Windows"]:
+    config.available_features.add("ansi-escapes")
+    # It is not clear that the escape sequence \x1b is portable across sed
+    # implementations, but that is ok because python substitutes the raw
+    # character here.
+    config.substitutions.append(
+        (
+            "%{reveal-ansi-escapes}",
+            "sed "
+            # Foreground colors.
+            "-e 's/\x1b\\[0;30m/<black>/g' "
+            "-e 's/\x1b\\[0;31m/<red>/g' "
+            "-e 's/\x1b\\[0;32m/<green>/g' "
+            "-e 's/\x1b\\[0;33m/<yellow>/g' "
+            "-e 's/\x1b\\[0;34m/<blue>/g' "
+            "-e 's/\x1b\\[0;35m/<magenta>/g' "
+            "-e 's/\x1b\\[0;36m/<cyan>/g' "
+            "-e 's/\x1b\\[0;37m/<white>/g' "
+            "-e 's/\x1b\\[0;90m/<bright-black>/g' "
+            "-e 's/\x1b\\[0;91m/<bright-red>/g' "
+            "-e 's/\x1b\\[0;92m/<bright-green>/g' "
+            "-e 's/\x1b\\[0;93m/<bright-yellow>/g' "
+            "-e 's/\x1b\\[0;94m/<bright-blue>/g' "
+            "-e 's/\x1b\\[0;95m/<bright-magenta>/g' "
+            "-e 's/\x1b\\[0;96m/<bright-cyan>/g' "
+            "-e 's/\x1b\\[0;97m/<bright-white>/g' "
+            # Bold foreground colors.
+            "-e 's/\x1b\\[0;1;30m/<bold-black>/g' "
+            "-e 's/\x1b\\[0;1;31m/<bold-red>/g' "
+            "-e 's/\x1b\\[0;1;32m/<bold-green>/g' "
+            "-e 's/\x1b\\[0;1;33m/<bold-yellow>/g' "
+            "-e 's/\x1b\\[0;1;34m/<bold-blue>/g' "
+            "-e 's/\x1b\\[0;1;35m/<bold-magenta>/g' "
+            "-e 's/\x1b\\[0;1;36m/<bold-cyan>/g' "
+            "-e 's/\x1b\\[0;1;37m/<bold-white>/g' "
+            "-e 's/\x1b\\[0;1;90m/<bold-bright-black>/g' "
+            "-e 's/\x1b\\[0;1;91m/<bold-bright-red>/g' "
+            "-e 's/\x1b\\[0;1;92m/<bold-bright-green>/g' "
+            "-e 's/\x1b\\[0;1;93m/<bold-bright-yellow>/g' "
+            "-e 's/\x1b\\[0;1;94m/<bold-bright-blue>/g' "
+            "-e 's/\x1b\\[0;1;95m/<bold-bright-magenta>/g' "
+            "-e 's/\x1b\\[0;1;96m/<bold-bright-cyan>/g' "
+            "-e 's/\x1b\\[0;1;97m/<bold-bright-white>/g' "
+            # Background colors.
+            "-e 's/\x1b\\[0;40m/<bg-black>/g' "
+            "-e 's/\x1b\\[0;41m/<bg-red>/g' "
+            "-e 's/\x1b\\[0;42m/<bg-green>/g' "
+            "-e 's/\x1b\\[0;43m/<bg-yellow>/g' "
+            "-e 's/\x1b\\[0;44m/<bg-blue>/g' "
+            "-e 's/\x1b\\[0;45m/<bg-magenta>/g' "
+            "-e 's/\x1b\\[0;46m/<bg-cyan>/g' "
+            "-e 's/\x1b\\[0;47m/<bg-white>/g' "
+            "-e 's/\x1b\\[0;100m/<bg-bright-black>/g' "
+            "-e 's/\x1b\\[0;101m/<bg-bright-red>/g' "
+            "-e 's/\x1b\\[0;102m/<bg-bright-green>/g' "
+            "-e 's/\x1b\\[0;103m/<bg-bright-yellow>/g' "
+            "-e 's/\x1b\\[0;104m/<bg-bright-blue>/g' "
+            "-e 's/\x1b\\[0;105m/<bg-bright-magenta>/g' "
+            "-e 's/\x1b\\[0;106m/<bg-bright-cyan>/g' "
+            "-e 's/\x1b\\[0;107m/<bg-bright-white>/g' "
+            # Bold background colors.
+            "-e 's/\x1b\\[0;1;40m/<bg-bold-black>/g' "
+            "-e 's/\x1b\\[0;1;41m/<bg-bold-red>/g' "
+            "-e 's/\x1b\\[0;1;42m/<bg-bold-green>/g' "
+            "-e 's/\x1b\\[0;1;43m/<bg-bold-yellow>/g' "
+            "-e 's/\x1b\\[0;1;44m/<bg-bold-blue>/g' "
+            "-e 's/\x1b\\[0;1;45m/<bg-bold-magenta>/g' "
+            "-e 's/\x1b\\[0;1;46m/<bg-bold-cyan>/g' "
+            "-e 's/\x1b\\[0;1;47m/<bg-bold-white>/g' "
+            "-e 's/\x1b\\[0;1;100m/<bg-bold-bright-black>/g' "
+            "-e 's/\x1b\\[0;1;101m/<bg-bold-bright-red>/g' "
+            "-e 's/\x1b\\[0;1;102m/<bg-bold-bright-green>/g' "
+            "-e 's/\x1b\\[0;1;103m/<bg-bold-bright-yellow>/g' "
+            "-e 's/\x1b\\[0;1;104m/<bg-bold-bright-blue>/g' "
+            "-e 's/\x1b\\[0;1;105m/<bg-bold-bright-magenta>/g' "
+            "-e 's/\x1b\\[0;1;106m/<bg-bold-bright-cyan>/g' "
+            "-e 's/\x1b\\[0;1;107m/<bg-bold-bright-white>/g' "
+            # Misc.
+            "-e 's/\x1b\\[1m/<bold>/g' "
+            "-e 's/\x1b\\[7m/<reverse>/g' "
+            "-e 's/\x1b\\[0m/<reset>/g' "
+            # Reveal any sequence we missed above.
+            "-e 's/\x1b/<esc>/g' ",
+        )
+    )
+
 if lit_config.update_tests:
     import sys
     import os

diff  --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 8668ea86a6ef9..d507cf2e74051 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -856,7 +856,7 @@ static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines,
   unsigned EllipsisLines = 3;
   if (EllipsisLines < StringRef(ElidedLines).count('\n')) {
     for (unsigned i = 0; i < EllipsisLines; ++i) {
-      WithColor(OS, raw_ostream::BLACK, /*Bold=*/true)
+      WithColor(OS, raw_ostream::BRIGHT_BLACK, /*Bold=*/true)
           << right_justify(".", LabelWidthGlobal);
       OS << '\n';
     }
@@ -980,7 +980,7 @@ static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req,
     }
 
     // Print right-aligned line number.
-    WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false,
+    WithColor(*LineOS, raw_ostream::BRIGHT_BLACK, /*Bold=*/true, /*BG=*/false,
               TheColorMode)
         << format_decimal(Line, LabelWidthGlobal) << ": ";
 
@@ -1003,8 +1003,16 @@ static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req,
       WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false,
                     /*BG=*/false, TheColorMode);
       bool InMatch = false;
-      if (Req.Verbose)
-        COS.changeColor(raw_ostream::CYAN, true, true);
+      if (Req.Verbose) {
+        COS.changeColor(raw_ostream::CYAN, /*Bold=*/true, /*BG=*/true);
+      } else {
+        // Our goal is to use the output streams's default color so that input
+        // text is legibile in both light and dark themes.  SAVEDCOLOR above
+        // currently ignores the Bold=false there, so we override it with
+        // resetColor here, which ensures consistent colors with the resetColor
+        // below anyway.
+        COS.resetColor();
+      }
       for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) {
         bool WasInMatch = InMatch;
         InMatch = false;
@@ -1014,6 +1022,8 @@ static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req,
             break;
           }
         }
+        // If !Req.Verbose, FoundAndExpectedMatches is empty, so InMatch and
+        // WasInMatch remain false, so these color transitions never happen.
         if (!WasInMatch && InMatch)
           COS.resetColor();
         else if (WasInMatch && !InMatch)


        


More information about the llvm-commits mailing list