Py tooling: fix Windows terminal color output and handle color flags in compare.py (fixes #642) - #2297
Py tooling: fix Windows terminal color output and handle color flags in compare.py (fixes #642)#2297jdymitarai wants to merge 4 commits into
Conversation
…in compare.py (fixes google#642) * Enable virtual terminal processing on Windows console handles when supported so ANSI escape codes are rendered natively instead of printing literal '^[[92m' sequences. * Fall back to disabled colors when stdout is not a TTY or when the standard NO_COLOR environment variable is present. * Support --color and --no-color on the root parser and correctly extract --color, --no-color, and --benchmark_color=false|true|auto if provided after subcommands. * Prevent false warnings in check_inputs when --benchmark_color is provided with JSON inputs. * Fix 'import util' in tools/gbench/report.py test to 'from gbench import util'. * Add unit tests covering color flag parsing, option extraction, and NO_COLOR detection.
8799b00 to
80784e6
Compare
|
|
||
| parser.add_argument( | ||
| color_group = parser.add_mutually_exclusive_group() | ||
| color_group.add_argument( |
There was a problem hiding this comment.
isn't the opposite of --no-color just --color? why do we need both flags?
There was a problem hiding this comment.
+1. I do not get why --color needs to exist. This is the default already.
|
Previously, \compare.py\ only had --no-color\ (with \default=True), which meant color could only be turned off, and could never be explicitly forced on (e.g., when piping output into \less -R\ or in CI runners where stdout is not detected as an interactive TTY). We added --color\ to allow explicitly forcing color output, while retaining --no-color\ for 100% backwards compatibility with existing user scripts and CLI habits. When neither flag is specified (\default=None), \compare.py\ auto-detects whether stdout is an interactive TTY and whether VT processing is supported (and honors the standard \NO_COLOR\ environment variable). |
that explains what it does, but not why. why do we need to force color on? |
|
(This also probably needs LLM disclosure notice.) |
|
Thanks for the feedback! Why force color on?When compare.py runs in non-interactive environments—such as CI pipelines (GitHub Actions, GitLab CI, Buildkite) or when piping through pagers/utilities (compare.py ... | less -R or compare.py ... | tee log.txt)—sys.stdout.isatty() evaluates to False. Without a flag to explicitly force color on, the output will unconditionally strip colors in those environments, even though modern CI dashboards and pagers like less -R fully support rendering ANSI color codes. Allowing users to pass --color mirrors standard CLI behavior across tooling (e.g., git --color=always, grep --color=always, pytest --color=yes, ls --color=always) so that colored diffs can be preserved in CI logs and terminal pipelines. Disclosure: AI assistance (LLM) was used in formulating this patch and tests. |
| # When both sides are JSON the only supported flag is | ||
| # --benchmark_filter= | ||
| for flag in util.remove_benchmark_flags("--benchmark_filter=", flags): | ||
| if flag.startswith("--benchmark_color="): |
|
i guess i don't have a particularly strong opinion. it's a lot of code to add for a fairly niche use-case. |
|
I have a question. If this change itself does not enable the coloring, I don't really know much about windows terminal, |
|
Hi @LebedevRI, thanks for asking! Let me clarify both points: 1. Does this enable coloring automatically?Yes, it does enable coloring automatically out-of-the-box without requiring any extra CLI arguments. For any interactive terminal session (Linux, macOS, and Windows):
|
LebedevRI
left a comment
There was a problem hiding this comment.
To make this less contentious, i'd propose
- Drop
--color. The default is already always-on with--no-coloropt-out. - Drop
--benchmark_color=handling. - Keep
enable_virtual_terminal_processing() - Adjust
should_use_color()into a legality check - should auto-disable colors when coloring is not possible.
…r, keep VT enable and legality check
|
Hi @LebedevRI, Thank you for the constructive suggestion! I have updated the PR following your 4 points:
The PR diff is now much smaller and focused strictly on Windows VT console support and the legality check. All tests pass. |
There was a problem hiding this comment.
| default=should_use_color(), |
| ) | ||
| self.assertFalse(parsed.color) | ||
|
|
||
| def test_benchmarks_color_default_true(self): |
There was a problem hiding this comment.
I think we can't test what the default is.
| class TestShouldUseColor(unittest.TestCase): | ||
| def test_should_use_color_no_color_env(self): | ||
| orig = os.environ.get("NO_COLOR") | ||
| try: | ||
| os.environ["NO_COLOR"] = "1" | ||
| self.assertFalse(should_use_color()) | ||
| finally: | ||
| if orig is None: | ||
| os.environ.pop("NO_COLOR", None) | ||
| else: | ||
| os.environ["NO_COLOR"] = orig |
There was a problem hiding this comment.
This test does not look hermetic.
Can unittest.mock.patch be used here?
|
Thanks @LebedevRI for the review! I have applied all three suggestions in commit \57ffcd9:
All 16 unit tests pass. |
| self.assertFalse(should_use_color()) | ||
|
|
||
| @patch.dict(os.environ, {}, clear=True) | ||
| @patch("sys.stdout.isatty", return_value=False) | ||
| def test_should_use_color_not_a_tty(self, mock_isatty): | ||
| self.assertFalse(should_use_color()) |
There was a problem hiding this comment.
I'd think this should test the actual result of parsing, not should_use_color().
|
|
||
| def test_json_diff_report_pretty_printing(self): | ||
| import util | ||
| from gbench import util |
There was a problem hiding this comment.
Hm, is this to allow running tests from prebuilt package?
I'm mainly worried that it would no longer be possible to run tests in source checkout.
Summary
This PR fixes issue #642 where
compare.pyproduced literal ANSI escape characters (^[[92m,^[[0m, etc.) on Windows consoles, and ensures that color settings (--color,--no-color,--benchmark_color=...) are properly recognized regardless of argument position.Root Cause
\033[...]) requireENABLE_VIRTUAL_TERMINAL_PROCESSING(0x0004) to be set on the console output buffer. Without this flag,cmd.exeand standard console hosts print literal escape sequences.compare.pypreviously defaultedcolor=Trueunconditionally, even when stdout was not a TTY (such as redirected to a file or piped to another tool), or when the standardNO_COLORenvironment variable was set.compare.pycollect trailing options intobenchmark_options(nargs=argparse.REMAINDER), placing--no-coloror--benchmark_color=falseafter the subcommand or arguments caused the flag to be captured as benchmark runner options instead of settingargs.color, while triggering a spurious warning when inputs were JSON files.tools/gbench/report.pyline 1492,import utilcausedModuleNotFoundErrorwhen run viapython -m unittest.Changes
ENABLE_VIRTUAL_TERMINAL_PROCESSINGon Windows console handles when available, and disable color formatting if VT mode is not supported by the console.sys.stdout.isatty()) and respectNO_COLORstandard (https://no-color.org/).--colorand--no-coloron root parser, and extract--color,--no-color, and--benchmark_color=false|true|autofrom trailingbenchmark_options.check_inputs.from gbench import utilinreport.py, and add unit tests incompare.pycovering color flag parsing, option resolution, andNO_COLORhandling.AUTHORSandCONTRIBUTORS.Verification
tools/pass:python -m unittest discover -s tools -p "*.py"