Skip to content

Commit 55cb946

Browse files
hugovkserhiy-storchakavstinner
committed
[3.13] pythongh-128595: Default to stdout isatty for colour detection instead of stderr (pythonGH-128498)
(cherry picked from commit 6f167d7) Co-authored-by: Hugo van Kemenade <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]> Co-authored-by: Victor Stinner <[email protected]>
1 parent 0351ed5 commit 55cb946

File tree

8 files changed

+48
-18
lines changed

8 files changed

+48
-18
lines changed

Lib/_colorize.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@ class ANSIColors:
2424
setattr(NoColors, attr, "")
2525

2626

27-
def get_colors(colorize: bool = False) -> ANSIColors:
28-
if colorize or can_colorize():
27+
def get_colors(colorize: bool = False, *, file=None) -> ANSIColors:
28+
if colorize or can_colorize(file=file):
2929
return ANSIColors()
3030
else:
3131
return NoColors
3232

3333

34-
def can_colorize() -> bool:
34+
def can_colorize(*, file=None) -> bool:
35+
if file is None:
36+
file = sys.stdout
37+
3538
if not sys.flags.ignore_environment:
3639
if os.environ.get("PYTHON_COLORS") == "0":
3740
return False
@@ -47,7 +50,7 @@ def can_colorize() -> bool:
4750
if os.environ.get("TERM") == "dumb":
4851
return False
4952

50-
if not hasattr(sys.stderr, "fileno"):
53+
if not hasattr(file, "fileno"):
5154
return False
5255

5356
if sys.platform == "win32":
@@ -60,6 +63,6 @@ def can_colorize() -> bool:
6063
return False
6164

6265
try:
63-
return os.isatty(sys.stderr.fileno())
66+
return os.isatty(file.fileno())
6467
except io.UnsupportedOperation:
65-
return sys.stderr.isatty()
68+
return file.isatty()

Lib/doctest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1558,7 +1558,7 @@ def out(s):
15581558
save_displayhook = sys.displayhook
15591559
sys.displayhook = sys.__displayhook__
15601560
saved_can_colorize = _colorize.can_colorize
1561-
_colorize.can_colorize = lambda: False
1561+
_colorize.can_colorize = lambda *args, **kwargs: False
15621562
color_variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None}
15631563
for key in color_variables:
15641564
color_variables[key] = os.environ.pop(key, None)

Lib/test/libregrtest/single.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ def test_func():
161161
def _runtest_env_changed_exc(result: TestResult, runtests: RunTests,
162162
display_failure: bool = True) -> None:
163163
# Handle exceptions, detect environment changes.
164+
stdout = get_colors(file=sys.stdout)
165+
stderr = get_colors(file=sys.stderr)
164166

165167
# Reset the environment_altered flag to detect if a test altered
166168
# the environment
@@ -181,28 +183,34 @@ def _runtest_env_changed_exc(result: TestResult, runtests: RunTests,
181183
_load_run_test(result, runtests)
182184
except support.ResourceDenied as exc:
183185
if not quiet and not pgo:
184-
print(f"{test_name} skipped -- {exc}", flush=True)
186+
print(
187+
f"{stdout.YELLOW}{test_name} skipped -- {exc}{stdout.RESET}",
188+
flush=True,
189+
)
185190
result.state = State.RESOURCE_DENIED
186191
return
187192
except unittest.SkipTest as exc:
188193
if not quiet and not pgo:
189-
print(f"{test_name} skipped -- {exc}", flush=True)
194+
print(
195+
f"{stdout.YELLOW}{test_name} skipped -- {exc}{stdout.RESET}",
196+
flush=True,
197+
)
190198
result.state = State.SKIPPED
191199
return
192200
except support.TestFailedWithDetails as exc:
193-
msg = f"test {test_name} failed"
201+
msg = f"{stderr.RED}test {test_name} failed{stderr.RESET}"
194202
if display_failure:
195-
msg = f"{msg} -- {exc}"
203+
msg = f"{stderr.RED}{msg} -- {exc}{stderr.RESET}"
196204
print(msg, file=sys.stderr, flush=True)
197205
result.state = State.FAILED
198206
result.errors = exc.errors
199207
result.failures = exc.failures
200208
result.stats = exc.stats
201209
return
202210
except support.TestFailed as exc:
203-
msg = f"test {test_name} failed"
211+
msg = f"{stderr.RED}test {test_name} failed{stderr.RESET}"
204212
if display_failure:
205-
msg = f"{msg} -- {exc}"
213+
msg = f"{stderr.RED}{msg} -- {exc}{stderr.RESET}"
206214
print(msg, file=sys.stderr, flush=True)
207215
result.state = State.FAILED
208216
result.stats = exc.stats
@@ -217,8 +225,11 @@ def _runtest_env_changed_exc(result: TestResult, runtests: RunTests,
217225
except:
218226
if not pgo:
219227
msg = traceback.format_exc()
220-
print(f"test {test_name} crashed -- {msg}",
221-
file=sys.stderr, flush=True)
228+
print(
229+
f"{stderr.RED}test {test_name} crashed -- {msg}{stderr.RESET}",
230+
file=sys.stderr,
231+
flush=True,
232+
)
222233
result.state = State.UNCAUGHT_EXC
223234
return
224235

@@ -300,6 +311,9 @@ def run_single_test(test_name: TestName, runtests: RunTests) -> TestResult:
300311
If runtests.use_junit, xml_data is a list containing each generated
301312
testsuite element.
302313
"""
314+
ansi = get_colors(file=sys.stderr)
315+
red, reset, yellow = ansi.BOLD_RED, ansi.RESET, ansi.YELLOW
316+
303317
start_time = time.perf_counter()
304318
result = TestResult(test_name)
305319
pgo = runtests.pgo

Lib/test/support/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2700,7 +2700,7 @@ def no_color():
27002700
from .os_helper import EnvironmentVarGuard
27012701

27022702
with (
2703-
swap_attr(_colorize, "can_colorize", lambda: False),
2703+
swap_attr(_colorize, "can_colorize", lambda file=None: False),
27042704
EnvironmentVarGuard() as env,
27052705
):
27062706
for var in {"FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS"}:

Lib/traceback.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
135135

136136
def _print_exception_bltin(exc, /):
137137
file = sys.stderr if sys.stderr is not None else sys.__stderr__
138-
colorize = _colorize.can_colorize()
138+
colorize = _colorize.can_colorize(file=file)
139139
return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize)
140140

141141

Lib/unittest/result.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,10 @@ def _exc_info_to_string(self, err, test):
189189
tb_e = traceback.TracebackException(
190190
exctype, value, tb,
191191
capture_locals=self.tb_locals, compact=True)
192-
msgLines = list(tb_e.format())
192+
from _colorize import can_colorize
193+
194+
colorize = hasattr(self, "stream") and can_colorize(file=self.stream)
195+
msgLines = list(tb_e.format(colorize=colorize))
193196

194197
if self.buffer:
195198
output = sys.stdout.getvalue()

Lib/unittest/runner.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def __init__(self, stream, descriptions, verbosity, *, durations=None):
4343
self.showAll = verbosity > 1
4444
self.dots = verbosity == 1
4545
self.descriptions = descriptions
46+
self._ansi = get_colors(file=stream)
4647
self._newline = True
4748
self.durations = durations
4849

@@ -267,6 +268,13 @@ def run(self, test):
267268
expectedFails, unexpectedSuccesses, skipped = results
268269

269270
infos = []
271+
ansi = get_colors(file=self.stream)
272+
bold_red = ansi.BOLD_RED
273+
green = ansi.GREEN
274+
red = ansi.RED
275+
reset = ansi.RESET
276+
yellow = ansi.YELLOW
277+
270278
if not result.wasSuccessful():
271279
self.stream.write("FAILED")
272280
failed, errored = len(result.failures), len(result.errors)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Default to stdout isatty for color detection instead of stderr. Patch by
2+
Hugo van Kemenade.

0 commit comments

Comments
 (0)