Skip to content

Commit afb9dc8

Browse files
gh-128595: Add test class helper to force no terminal colour (#128687)
Co-authored-by: Erlend E. Aasland <[email protected]>
1 parent ddd9599 commit afb9dc8

File tree

5 files changed

+49
-22
lines changed

5 files changed

+49
-22
lines changed

Lib/test/support/__init__.py

+31-16
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"skip_on_s390x",
6161
"without_optimizer",
6262
"force_not_colorized",
63+
"force_not_colorized_test_class",
6364
"BrokenIter",
6465
"in_systemd_nspawn_sync_suppressed",
6566
"run_no_yield_async_fn", "run_yielding_async_fn", "async_yield",
@@ -2832,30 +2833,44 @@ def is_slot_wrapper(name, value):
28322833
yield name, True
28332834

28342835

2836+
@contextlib.contextmanager
2837+
def no_color():
2838+
import _colorize
2839+
from .os_helper import EnvironmentVarGuard
2840+
2841+
with (
2842+
swap_attr(_colorize, "can_colorize", lambda: False),
2843+
EnvironmentVarGuard() as env,
2844+
):
2845+
for var in {"FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS"}:
2846+
env.unset(var)
2847+
env.set("NO_COLOR", "1")
2848+
yield
2849+
2850+
28352851
def force_not_colorized(func):
28362852
"""Force the terminal not to be colorized."""
28372853
@functools.wraps(func)
28382854
def wrapper(*args, **kwargs):
2839-
import _colorize
2840-
original_fn = _colorize.can_colorize
2841-
variables: dict[str, str | None] = {
2842-
"PYTHON_COLORS": None, "FORCE_COLOR": None, "NO_COLOR": None
2843-
}
2844-
try:
2845-
for key in variables:
2846-
variables[key] = os.environ.pop(key, None)
2847-
os.environ["NO_COLOR"] = "1"
2848-
_colorize.can_colorize = lambda: False
2855+
with no_color():
28492856
return func(*args, **kwargs)
2850-
finally:
2851-
_colorize.can_colorize = original_fn
2852-
del os.environ["NO_COLOR"]
2853-
for key, value in variables.items():
2854-
if value is not None:
2855-
os.environ[key] = value
28562857
return wrapper
28572858

28582859

2860+
def force_not_colorized_test_class(cls):
2861+
"""Force the terminal not to be colorized for the entire test class."""
2862+
original_setUpClass = cls.setUpClass
2863+
2864+
@classmethod
2865+
@functools.wraps(cls.setUpClass)
2866+
def new_setUpClass(cls):
2867+
cls.enterClassContext(no_color())
2868+
original_setUpClass()
2869+
2870+
cls.setUpClass = new_setUpClass
2871+
return cls
2872+
2873+
28592874
def initialized_with_pyrepl():
28602875
"""Detect whether PyREPL was used during Python initialization."""
28612876
# If the main module has a __file__ attribute it's a Python module, which means PyREPL.

Lib/test/test_code_module.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
from textwrap import dedent
66
from contextlib import ExitStack
77
from unittest import mock
8+
from test.support import force_not_colorized_test_class
89
from test.support import import_helper
910

10-
1111
code = import_helper.import_module('code')
1212

1313

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

3232

33+
@force_not_colorized_test_class
3334
class TestInteractiveConsole(unittest.TestCase, MockSys):
3435
maxDiff = None
3536

Lib/test/test_exceptions.py

+1
Original file line numberDiff line numberDiff line change
@@ -2274,6 +2274,7 @@ def test_range_of_offsets(self):
22742274
self.assertIn(expected, err.getvalue())
22752275
the_exception = exc
22762276

2277+
@force_not_colorized
22772278
def test_subclass(self):
22782279
class MySyntaxError(SyntaxError):
22792280
pass

Lib/test/test_traceback.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from test.support.os_helper import TESTFN, unlink
2222
from test.support.script_helper import assert_python_ok, assert_python_failure
2323
from test.support.import_helper import forget
24-
from test.support import force_not_colorized
24+
from test.support import force_not_colorized, force_not_colorized_test_class
2525

2626
import json
2727
import textwrap
@@ -1712,6 +1712,7 @@ def f():
17121712

17131713

17141714
@requires_debug_ranges()
1715+
@force_not_colorized_test_class
17151716
class PurePythonTracebackErrorCaretTests(
17161717
PurePythonExceptionFormattingMixin,
17171718
TracebackErrorLocationCaretTestBase,
@@ -1725,6 +1726,7 @@ class PurePythonTracebackErrorCaretTests(
17251726

17261727
@cpython_only
17271728
@requires_debug_ranges()
1729+
@force_not_colorized_test_class
17281730
class CPythonTracebackErrorCaretTests(
17291731
CAPIExceptionFormattingMixin,
17301732
TracebackErrorLocationCaretTestBase,
@@ -1736,6 +1738,7 @@ class CPythonTracebackErrorCaretTests(
17361738

17371739
@cpython_only
17381740
@requires_debug_ranges()
1741+
@force_not_colorized_test_class
17391742
class CPythonTracebackLegacyErrorCaretTests(
17401743
CAPIExceptionFormattingLegacyMixin,
17411744
TracebackErrorLocationCaretTestBase,
@@ -2149,10 +2152,12 @@ def test_print_exception_bad_type_python(self):
21492152
boundaries = re.compile(
21502153
'(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
21512154

2155+
@force_not_colorized_test_class
21522156
class TestTracebackFormat(unittest.TestCase, TracebackFormatMixin):
21532157
pass
21542158

21552159
@cpython_only
2160+
@force_not_colorized_test_class
21562161
class TestFallbackTracebackFormat(unittest.TestCase, TracebackFormatMixin):
21572162
DEBUG_RANGES = False
21582163
def setUp(self) -> None:
@@ -2940,6 +2945,7 @@ def f():
29402945
self.assertEqual(report, expected)
29412946

29422947

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

29582964

2965+
@force_not_colorized_test_class
29592966
class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
29602967
#
29612968
# This checks built-in reporting by the interpreter.

Lib/test/test_unittest/test_result.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import io
22
import sys
33
import textwrap
4-
5-
from test.support import warnings_helper, captured_stdout
6-
74
import traceback
85
import unittest
96
from unittest.util import strclass
10-
from test.support import force_not_colorized
7+
from test.support import warnings_helper
8+
from test.support import (
9+
captured_stdout,
10+
force_not_colorized,
11+
force_not_colorized_test_class,
12+
)
1113
from test.test_unittest.support import BufferedWriter
1214

1315

@@ -772,6 +774,7 @@ def testFoo(self):
772774
runner.run(Test('testFoo'))
773775

774776

777+
@force_not_colorized_test_class
775778
class TestOutputBuffering(unittest.TestCase):
776779

777780
def setUp(self):

0 commit comments

Comments
 (0)