[3.13] gh-128595: Add test class helper to force no terminal colour (GH-128687) (#128778)

Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
This commit is contained in:
Hugo van Kemenade 2025-01-13 13:57:44 +02:00 committed by GitHub
parent 05bd6cbe61
commit afcf238ed4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 44 additions and 21 deletions

View file

@ -60,6 +60,7 @@ __all__ = [
"skip_on_s390x", "skip_on_s390x",
"without_optimizer", "without_optimizer",
"force_not_colorized", "force_not_colorized",
"force_not_colorized_test_class",
"BrokenIter", "BrokenIter",
] ]
@ -2693,30 +2694,44 @@ def iter_slot_wrappers(cls):
yield name, True yield name, True
@contextlib.contextmanager
def no_color():
import _colorize
from .os_helper import EnvironmentVarGuard
with (
swap_attr(_colorize, "can_colorize", lambda: False),
EnvironmentVarGuard() as env,
):
for var in {"FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS"}:
env.unset(var)
env.set("NO_COLOR", "1")
yield
def force_not_colorized(func): def force_not_colorized(func):
"""Force the terminal not to be colorized.""" """Force the terminal not to be colorized."""
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
import _colorize with no_color():
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
return func(*args, **kwargs) 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
return wrapper return wrapper
def force_not_colorized_test_class(cls):
"""Force the terminal not to be colorized for the entire test class."""
original_setUpClass = cls.setUpClass
@classmethod
@functools.wraps(cls.setUpClass)
def new_setUpClass(cls):
cls.enterClassContext(no_color())
original_setUpClass()
cls.setUpClass = new_setUpClass
return cls
def initialized_with_pyrepl(): def initialized_with_pyrepl():
"""Detect whether PyREPL was used during Python initialization.""" """Detect whether PyREPL was used during Python initialization."""
# If the main module has a __file__ attribute it's a Python module, which means PyREPL. # If the main module has a __file__ attribute it's a Python module, which means PyREPL.

View file

@ -5,9 +5,9 @@ import unittest
from textwrap import dedent from textwrap import dedent
from contextlib import ExitStack from contextlib import ExitStack
from unittest import mock from unittest import mock
from test.support import force_not_colorized_test_class
from test.support import import_helper from test.support import import_helper
code = import_helper.import_module('code') code = import_helper.import_module('code')
@ -30,6 +30,7 @@ class MockSys:
del self.sysmod.ps2 del self.sysmod.ps2
@force_not_colorized_test_class
class TestInteractiveConsole(unittest.TestCase, MockSys): class TestInteractiveConsole(unittest.TestCase, MockSys):
maxDiff = None maxDiff = None

View file

@ -21,7 +21,7 @@ from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ,
from test.support.os_helper import TESTFN, unlink from test.support.os_helper import TESTFN, unlink
from test.support.script_helper import assert_python_ok, assert_python_failure from test.support.script_helper import assert_python_ok, assert_python_failure
from test.support.import_helper import forget 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 json
import textwrap import textwrap
@ -1709,6 +1709,7 @@ class TracebackErrorLocationCaretTestBase:
@requires_debug_ranges() @requires_debug_ranges()
@force_not_colorized_test_class
class PurePythonTracebackErrorCaretTests( class PurePythonTracebackErrorCaretTests(
PurePythonExceptionFormattingMixin, PurePythonExceptionFormattingMixin,
TracebackErrorLocationCaretTestBase, TracebackErrorLocationCaretTestBase,
@ -1722,6 +1723,7 @@ class PurePythonTracebackErrorCaretTests(
@cpython_only @cpython_only
@requires_debug_ranges() @requires_debug_ranges()
@force_not_colorized_test_class
class CPythonTracebackErrorCaretTests( class CPythonTracebackErrorCaretTests(
CAPIExceptionFormattingMixin, CAPIExceptionFormattingMixin,
TracebackErrorLocationCaretTestBase, TracebackErrorLocationCaretTestBase,
@ -1733,6 +1735,7 @@ class CPythonTracebackErrorCaretTests(
@cpython_only @cpython_only
@requires_debug_ranges() @requires_debug_ranges()
@force_not_colorized_test_class
class CPythonTracebackLegacyErrorCaretTests( class CPythonTracebackLegacyErrorCaretTests(
CAPIExceptionFormattingLegacyMixin, CAPIExceptionFormattingLegacyMixin,
TracebackErrorLocationCaretTestBase, TracebackErrorLocationCaretTestBase,
@ -2144,10 +2147,12 @@ context_message = (
boundaries = re.compile( boundaries = re.compile(
'(%s|%s)' % (re.escape(cause_message), re.escape(context_message))) '(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
@force_not_colorized_test_class
class TestTracebackFormat(unittest.TestCase, TracebackFormatMixin): class TestTracebackFormat(unittest.TestCase, TracebackFormatMixin):
pass pass
@cpython_only @cpython_only
@force_not_colorized_test_class
class TestFallbackTracebackFormat(unittest.TestCase, TracebackFormatMixin): class TestFallbackTracebackFormat(unittest.TestCase, TracebackFormatMixin):
DEBUG_RANGES = False DEBUG_RANGES = False
def setUp(self) -> None: def setUp(self) -> None:
@ -2935,6 +2940,7 @@ class BaseExceptionReportingTests:
self.assertEqual(report, expected) self.assertEqual(report, expected)
@force_not_colorized_test_class
class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase): class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
# #
# This checks reporting through the 'traceback' module, with both # This checks reporting through the 'traceback' module, with both
@ -2951,6 +2957,7 @@ class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
return s return s
@force_not_colorized_test_class
class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase): class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
# #
# This checks built-in reporting by the interpreter. # This checks built-in reporting by the interpreter.

View file

@ -1,12 +1,11 @@
import io import io
import sys import sys
import textwrap import textwrap
from test.support import warnings_helper, captured_stdout
import traceback import traceback
import unittest import unittest
from unittest.util import strclass from unittest.util import strclass
from test.support import warnings_helper
from test.support import captured_stdout, force_not_colorized_test_class
from test.test_unittest.support import BufferedWriter from test.test_unittest.support import BufferedWriter
@ -758,6 +757,7 @@ class Test_OldTestResult(unittest.TestCase):
runner.run(Test('testFoo')) runner.run(Test('testFoo'))
@force_not_colorized_test_class
class TestOutputBuffering(unittest.TestCase): class TestOutputBuffering(unittest.TestCase):
def setUp(self): def setUp(self):