mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
gh-128595: Default to stdout isatty for colour detection instead of stderr (#128498)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
parent
a429159797
commit
6f167d7134
8 changed files with 38 additions and 23 deletions
|
@ -26,14 +26,17 @@ for attr in dir(NoColors):
|
||||||
setattr(NoColors, attr, "")
|
setattr(NoColors, attr, "")
|
||||||
|
|
||||||
|
|
||||||
def get_colors(colorize: bool = False) -> ANSIColors:
|
def get_colors(colorize: bool = False, *, file=None) -> ANSIColors:
|
||||||
if colorize or can_colorize():
|
if colorize or can_colorize(file=file):
|
||||||
return ANSIColors()
|
return ANSIColors()
|
||||||
else:
|
else:
|
||||||
return NoColors
|
return NoColors
|
||||||
|
|
||||||
|
|
||||||
def can_colorize() -> bool:
|
def can_colorize(*, file=None) -> bool:
|
||||||
|
if file is None:
|
||||||
|
file = sys.stdout
|
||||||
|
|
||||||
if not sys.flags.ignore_environment:
|
if not sys.flags.ignore_environment:
|
||||||
if os.environ.get("PYTHON_COLORS") == "0":
|
if os.environ.get("PYTHON_COLORS") == "0":
|
||||||
return False
|
return False
|
||||||
|
@ -49,7 +52,7 @@ def can_colorize() -> bool:
|
||||||
if os.environ.get("TERM") == "dumb":
|
if os.environ.get("TERM") == "dumb":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not hasattr(sys.stderr, "fileno"):
|
if not hasattr(file, "fileno"):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
@ -62,6 +65,6 @@ def can_colorize() -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return os.isatty(sys.stderr.fileno())
|
return os.isatty(file.fileno())
|
||||||
except io.UnsupportedOperation:
|
except io.UnsupportedOperation:
|
||||||
return sys.stderr.isatty()
|
return file.isatty()
|
||||||
|
|
|
@ -1558,7 +1558,7 @@ class DocTestRunner:
|
||||||
save_displayhook = sys.displayhook
|
save_displayhook = sys.displayhook
|
||||||
sys.displayhook = sys.__displayhook__
|
sys.displayhook = sys.__displayhook__
|
||||||
saved_can_colorize = _colorize.can_colorize
|
saved_can_colorize = _colorize.can_colorize
|
||||||
_colorize.can_colorize = lambda: False
|
_colorize.can_colorize = lambda *args, **kwargs: False
|
||||||
color_variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None}
|
color_variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None}
|
||||||
for key in color_variables:
|
for key in color_variables:
|
||||||
color_variables[key] = os.environ.pop(key, None)
|
color_variables[key] = os.environ.pop(key, None)
|
||||||
|
|
|
@ -162,8 +162,8 @@ def _load_run_test(result: TestResult, runtests: RunTests) -> None:
|
||||||
def _runtest_env_changed_exc(result: TestResult, runtests: RunTests,
|
def _runtest_env_changed_exc(result: TestResult, runtests: RunTests,
|
||||||
display_failure: bool = True) -> None:
|
display_failure: bool = True) -> None:
|
||||||
# Handle exceptions, detect environment changes.
|
# Handle exceptions, detect environment changes.
|
||||||
ansi = get_colors()
|
stdout = get_colors(file=sys.stdout)
|
||||||
red, reset, yellow = ansi.RED, ansi.RESET, ansi.YELLOW
|
stderr = get_colors(file=sys.stderr)
|
||||||
|
|
||||||
# Reset the environment_altered flag to detect if a test altered
|
# Reset the environment_altered flag to detect if a test altered
|
||||||
# the environment
|
# the environment
|
||||||
|
@ -184,18 +184,24 @@ def _runtest_env_changed_exc(result: TestResult, runtests: RunTests,
|
||||||
_load_run_test(result, runtests)
|
_load_run_test(result, runtests)
|
||||||
except support.ResourceDenied as exc:
|
except support.ResourceDenied as exc:
|
||||||
if not quiet and not pgo:
|
if not quiet and not pgo:
|
||||||
print(f"{yellow}{test_name} skipped -- {exc}{reset}", flush=True)
|
print(
|
||||||
|
f"{stdout.YELLOW}{test_name} skipped -- {exc}{stdout.RESET}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
result.state = State.RESOURCE_DENIED
|
result.state = State.RESOURCE_DENIED
|
||||||
return
|
return
|
||||||
except unittest.SkipTest as exc:
|
except unittest.SkipTest as exc:
|
||||||
if not quiet and not pgo:
|
if not quiet and not pgo:
|
||||||
print(f"{yellow}{test_name} skipped -- {exc}{reset}", flush=True)
|
print(
|
||||||
|
f"{stdout.YELLOW}{test_name} skipped -- {exc}{stdout.RESET}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
result.state = State.SKIPPED
|
result.state = State.SKIPPED
|
||||||
return
|
return
|
||||||
except support.TestFailedWithDetails as exc:
|
except support.TestFailedWithDetails as exc:
|
||||||
msg = f"{red}test {test_name} failed{reset}"
|
msg = f"{stderr.RED}test {test_name} failed{stderr.RESET}"
|
||||||
if display_failure:
|
if display_failure:
|
||||||
msg = f"{red}{msg} -- {exc}{reset}"
|
msg = f"{stderr.RED}{msg} -- {exc}{stderr.RESET}"
|
||||||
print(msg, file=sys.stderr, flush=True)
|
print(msg, file=sys.stderr, flush=True)
|
||||||
result.state = State.FAILED
|
result.state = State.FAILED
|
||||||
result.errors = exc.errors
|
result.errors = exc.errors
|
||||||
|
@ -203,9 +209,9 @@ def _runtest_env_changed_exc(result: TestResult, runtests: RunTests,
|
||||||
result.stats = exc.stats
|
result.stats = exc.stats
|
||||||
return
|
return
|
||||||
except support.TestFailed as exc:
|
except support.TestFailed as exc:
|
||||||
msg = f"{red}test {test_name} failed{reset}"
|
msg = f"{stderr.RED}test {test_name} failed{stderr.RESET}"
|
||||||
if display_failure:
|
if display_failure:
|
||||||
msg = f"{red}{msg} -- {exc}{reset}"
|
msg = f"{stderr.RED}{msg} -- {exc}{stderr.RESET}"
|
||||||
print(msg, file=sys.stderr, flush=True)
|
print(msg, file=sys.stderr, flush=True)
|
||||||
result.state = State.FAILED
|
result.state = State.FAILED
|
||||||
result.stats = exc.stats
|
result.stats = exc.stats
|
||||||
|
@ -220,8 +226,11 @@ def _runtest_env_changed_exc(result: TestResult, runtests: RunTests,
|
||||||
except:
|
except:
|
||||||
if not pgo:
|
if not pgo:
|
||||||
msg = traceback.format_exc()
|
msg = traceback.format_exc()
|
||||||
print(f"{red}test {test_name} crashed -- {msg}{reset}",
|
print(
|
||||||
file=sys.stderr, flush=True)
|
f"{stderr.RED}test {test_name} crashed -- {msg}{stderr.RESET}",
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
result.state = State.UNCAUGHT_EXC
|
result.state = State.UNCAUGHT_EXC
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -303,7 +312,7 @@ def run_single_test(test_name: TestName, runtests: RunTests) -> TestResult:
|
||||||
If runtests.use_junit, xml_data is a list containing each generated
|
If runtests.use_junit, xml_data is a list containing each generated
|
||||||
testsuite element.
|
testsuite element.
|
||||||
"""
|
"""
|
||||||
ansi = get_colors()
|
ansi = get_colors(file=sys.stderr)
|
||||||
red, reset, yellow = ansi.BOLD_RED, ansi.RESET, ansi.YELLOW
|
red, reset, yellow = ansi.BOLD_RED, ansi.RESET, ansi.YELLOW
|
||||||
|
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
|
|
|
@ -2839,7 +2839,7 @@ def no_color():
|
||||||
from .os_helper import EnvironmentVarGuard
|
from .os_helper import EnvironmentVarGuard
|
||||||
|
|
||||||
with (
|
with (
|
||||||
swap_attr(_colorize, "can_colorize", lambda: False),
|
swap_attr(_colorize, "can_colorize", lambda file=None: False),
|
||||||
EnvironmentVarGuard() as env,
|
EnvironmentVarGuard() as env,
|
||||||
):
|
):
|
||||||
for var in {"FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS"}:
|
for var in {"FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS"}:
|
||||||
|
|
|
@ -135,7 +135,7 @@ BUILTIN_EXCEPTION_LIMIT = object()
|
||||||
|
|
||||||
def _print_exception_bltin(exc, /):
|
def _print_exception_bltin(exc, /):
|
||||||
file = sys.stderr if sys.stderr is not None else sys.__stderr__
|
file = sys.stderr if sys.stderr is not None else sys.__stderr__
|
||||||
colorize = _colorize.can_colorize()
|
colorize = _colorize.can_colorize(file=file)
|
||||||
return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize)
|
return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -191,7 +191,8 @@ class TestResult(object):
|
||||||
capture_locals=self.tb_locals, compact=True)
|
capture_locals=self.tb_locals, compact=True)
|
||||||
from _colorize import can_colorize
|
from _colorize import can_colorize
|
||||||
|
|
||||||
msgLines = list(tb_e.format(colorize=can_colorize()))
|
colorize = hasattr(self, "stream") and can_colorize(file=self.stream)
|
||||||
|
msgLines = list(tb_e.format(colorize=colorize))
|
||||||
|
|
||||||
if self.buffer:
|
if self.buffer:
|
||||||
output = sys.stdout.getvalue()
|
output = sys.stdout.getvalue()
|
||||||
|
|
|
@ -45,7 +45,7 @@ class TextTestResult(result.TestResult):
|
||||||
self.showAll = verbosity > 1
|
self.showAll = verbosity > 1
|
||||||
self.dots = verbosity == 1
|
self.dots = verbosity == 1
|
||||||
self.descriptions = descriptions
|
self.descriptions = descriptions
|
||||||
self._ansi = get_colors()
|
self._ansi = get_colors(file=stream)
|
||||||
self._newline = True
|
self._newline = True
|
||||||
self.durations = durations
|
self.durations = durations
|
||||||
|
|
||||||
|
@ -286,7 +286,7 @@ class TextTestRunner(object):
|
||||||
expected_fails, unexpected_successes, skipped = results
|
expected_fails, unexpected_successes, skipped = results
|
||||||
|
|
||||||
infos = []
|
infos = []
|
||||||
ansi = get_colors()
|
ansi = get_colors(file=self.stream)
|
||||||
bold_red = ansi.BOLD_RED
|
bold_red = ansi.BOLD_RED
|
||||||
green = ansi.GREEN
|
green = ansi.GREEN
|
||||||
red = ansi.RED
|
red = ansi.RED
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Default to stdout isatty for color detection instead of stderr. Patch by
|
||||||
|
Hugo van Kemenade.
|
Loading…
Add table
Add a link
Reference in a new issue