Skip to content

gh-128595: Add test class helper to force no terminal colour #128687

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 50 additions & 14 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import types
import unittest
import warnings
from collections.abc import Callable


__all__ = [
Expand Down Expand Up @@ -60,6 +61,7 @@
"skip_on_s390x",
"without_optimizer",
"force_not_colorized",
"force_not_colorized_test_class",
"BrokenIter",
"in_systemd_nspawn_sync_suppressed",
"run_no_yield_async_fn", "run_yielding_async_fn", "async_yield",
Expand Down Expand Up @@ -2832,29 +2834,63 @@ def is_slot_wrapper(name, value):
yield name, True


def _disable_terminal_color():
import _colorize

original_fn = _colorize.can_colorize
variables = {
"PYTHON_COLORS": None,
"FORCE_COLOR": None,
"NO_COLOR": None,
}
for key in variables:
variables[key] = os.environ.pop(key, None)
os.environ["NO_COLOR"] = "1"
_colorize.can_colorize = lambda: False
return original_fn, variables


def _re_enable_terminal_color(original_fn, variables):
import _colorize

_colorize.can_colorize = original_fn
del os.environ["NO_COLOR"]
for key, value in variables.items():
if value is not None:
os.environ[key] = value


def force_not_colorized(func):
"""Force the terminal not to be colorized."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
import _colorize
original_fn = _colorize.can_colorize
variables: dict[str, str | None] = {
"PYTHON_COLORS": None, "FORCE_COLOR": None, "NO_COLOR": None
}
try:
for key in variables:
variables[key] = os.environ.pop(key, None)
os.environ["NO_COLOR"] = "1"
_colorize.can_colorize = lambda: False
original_fn, variables = _disable_terminal_color()
return func(*args, **kwargs)
finally:
_colorize.can_colorize = original_fn
del os.environ["NO_COLOR"]
for key, value in variables.items():
if value is not None:
os.environ[key] = value
_re_enable_terminal_color(original_fn, variables)
return wrapper

def force_not_colorized_test_class(cls):
"""Force the terminal not to be colorized."""
original_setup = cls.setUp
original_teardown = cls.tearDown

@functools.wraps(cls.setUp)
def setUp_wrapper(self, *args, **kwargs):
self._original_fn, self._variables = _disable_terminal_color()

return original_setup(self, *args, **kwargs)

@functools.wraps(cls.tearDown)
def tearDown_wrapper(self, *args, **kwargs):
_re_enable_terminal_color(self._original_fn, self._variables)
return original_teardown(self, *args, **kwargs)

cls.setUp = setUp_wrapper
cls.tearDown = tearDown_wrapper
return cls


def initialized_with_pyrepl():
"""Detect whether PyREPL was used during Python initialization."""
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_code_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
from textwrap import dedent
from contextlib import ExitStack
from unittest import mock
from test.support import force_not_colorized_test_class
from test.support import import_helper


code = import_helper.import_module('code')


Expand All @@ -30,6 +30,7 @@ def mock_sys(self):
del self.sysmod.ps2


@force_not_colorized_test_class
class TestInteractiveConsole(unittest.TestCase, MockSys):
maxDiff = None

Expand Down
9 changes: 8 additions & 1 deletion Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from test.support.os_helper import TESTFN, unlink
from test.support.script_helper import assert_python_ok, assert_python_failure
from test.support.import_helper import forget
from test.support import force_not_colorized
from test.support import force_not_colorized, force_not_colorized_test_class

import json
import textwrap
Expand Down Expand Up @@ -1712,6 +1712,7 @@ def f():


@requires_debug_ranges()
@force_not_colorized_test_class
class PurePythonTracebackErrorCaretTests(
PurePythonExceptionFormattingMixin,
TracebackErrorLocationCaretTestBase,
Expand All @@ -1725,6 +1726,7 @@ class PurePythonTracebackErrorCaretTests(

@cpython_only
@requires_debug_ranges()
@force_not_colorized_test_class
class CPythonTracebackErrorCaretTests(
CAPIExceptionFormattingMixin,
TracebackErrorLocationCaretTestBase,
Expand All @@ -1736,6 +1738,7 @@ class CPythonTracebackErrorCaretTests(

@cpython_only
@requires_debug_ranges()
@force_not_colorized_test_class
class CPythonTracebackLegacyErrorCaretTests(
CAPIExceptionFormattingLegacyMixin,
TracebackErrorLocationCaretTestBase,
Expand Down Expand Up @@ -2149,10 +2152,12 @@ def test_print_exception_bad_type_python(self):
boundaries = re.compile(
'(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))

@force_not_colorized_test_class
class TestTracebackFormat(unittest.TestCase, TracebackFormatMixin):
pass

@cpython_only
@force_not_colorized_test_class
class TestFallbackTracebackFormat(unittest.TestCase, TracebackFormatMixin):
DEBUG_RANGES = False
def setUp(self) -> None:
Expand Down Expand Up @@ -2940,6 +2945,7 @@ def f():
self.assertEqual(report, expected)


@force_not_colorized_test_class
class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
#
# This checks reporting through the 'traceback' module, with both
Expand All @@ -2956,6 +2962,7 @@ def get_report(self, e):
return s


@force_not_colorized_test_class
class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
#
# This checks built-in reporting by the interpreter.
Expand Down
Loading