diff options
author | Pablo Galindo Salgado <Pablogsal@gmail.com> | 2024-04-24 20:25:22 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-24 20:25:22 (GMT) |
commit | 345e1e04ec72698a1e257c805b3840d9f55eb80d (patch) | |
tree | 7a96eb0b69525fa33dd552b4ba952c4c857e3896 | |
parent | 59a4d52973ca73bd739f914e88243a31dbef6b32 (diff) | |
download | cpython-345e1e04ec72698a1e257c805b3840d9f55eb80d.zip cpython-345e1e04ec72698a1e257c805b3840d9f55eb80d.tar.gz cpython-345e1e04ec72698a1e257c805b3840d9f55eb80d.tar.bz2 |
gh-112730: Make the test suite resilient to color-activation environment variables (#117672)
-rw-r--r-- | .github/workflows/reusable-ubuntu.yml | 1 | ||||
-rw-r--r-- | Lib/doctest.py | 10 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_run.py | 3 | ||||
-rw-r--r-- | Lib/test/support/__init__.py | 20 | ||||
-rw-r--r-- | Lib/test/test_cmd_line.py | 2 | ||||
-rw-r--r-- | Lib/test/test_exceptions.py | 7 | ||||
-rw-r--r-- | Lib/test/test_interpreters/test_api.py | 2 | ||||
-rw-r--r-- | Lib/test/test_sys.py | 5 | ||||
-rw-r--r-- | Lib/test/test_threading.py | 3 | ||||
-rw-r--r-- | Lib/test/test_traceback.py | 21 | ||||
-rw-r--r-- | Lib/test/test_tracemalloc.py | 2 | ||||
-rw-r--r-- | Lib/test/test_warnings/__init__.py | 3 | ||||
-rw-r--r-- | Lib/traceback.py | 26 |
13 files changed, 89 insertions, 16 deletions
diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index ee64fe6..e6fbaaf 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -14,6 +14,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-20.04 env: + FORCE_COLOR: 1 OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 steps: diff --git a/Lib/doctest.py b/Lib/doctest.py index a3b42fd..d8c632e 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1556,7 +1556,11 @@ class DocTestRunner: # Make sure sys.displayhook just prints the value to stdout save_displayhook = sys.displayhook sys.displayhook = sys.__displayhook__ - + saved_can_colorize = traceback._can_colorize + traceback._can_colorize = lambda: False + color_variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None} + for key in color_variables: + color_variables[key] = os.environ.pop(key, None) try: return self.__run(test, compileflags, out) finally: @@ -1565,6 +1569,10 @@ class DocTestRunner: sys.settrace(save_trace) linecache.getlines = self.save_linecache_getlines sys.displayhook = save_displayhook + traceback._can_colorize = saved_can_colorize + for key, value in color_variables.items(): + if value is not None: + os.environ[key] = value if clear_globs: test.globs.clear() import builtins diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py index a38e43d..83ecbff 100644 --- a/Lib/idlelib/idle_test/test_run.py +++ b/Lib/idlelib/idle_test/test_run.py @@ -8,6 +8,7 @@ import unittest from unittest import mock import idlelib from idlelib.idle_test.mock_idle import Func +from test.support import force_not_colorized idlelib.testing = True # Use {} for executing test user code. @@ -46,6 +47,7 @@ class ExceptionTest(unittest.TestCase): "Did you mean: 'real'?\n"), ) + @force_not_colorized def test_get_message(self): for code, exc, msg in self.data: with self.subTest(code=code): @@ -57,6 +59,7 @@ class ExceptionTest(unittest.TestCase): expect = f'{exc.__name__}: {msg}' self.assertEqual(actual, expect) + @force_not_colorized @mock.patch.object(run, 'cleanup_traceback', new_callable=lambda: (lambda t, e: None)) def test_get_multiple_message(self, mock): diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 6eb0f84..ea49454 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -59,6 +59,7 @@ __all__ = [ "Py_DEBUG", "exceeds_recursion_limit", "get_c_recursion_limit", "skip_on_s390x", "without_optimizer", + "force_not_colorized" ] @@ -2557,3 +2558,22 @@ def copy_python_src_ignore(path, names): 'build', } return ignored + +def force_not_colorized(func): + """Force the terminal not to be colorized.""" + @functools.wraps(func) + def wrapper(*args, **kwargs): + import traceback + original_fn = traceback._can_colorize + variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None} + try: + for key in variables: + variables[key] = os.environ.pop(key, None) + traceback._can_colorize = lambda: False + return func(*args, **kwargs) + finally: + traceback._can_colorize = original_fn + for key, value in variables.items(): + if value is not None: + os.environ[key] = value + return wrapper diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index fb832ae..9624d35 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -10,6 +10,7 @@ import textwrap import unittest from test import support from test.support import os_helper +from test.support import force_not_colorized from test.support.script_helper import ( spawn_python, kill_python, assert_python_ok, assert_python_failure, interpreter_requires_environment @@ -1027,6 +1028,7 @@ class IgnoreEnvironmentTest(unittest.TestCase): class SyntaxErrorTests(unittest.TestCase): + @force_not_colorized def check_string(self, code): proc = subprocess.run([sys.executable, "-"], input=code, stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 1224f14..3138f50 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -12,7 +12,8 @@ from textwrap import dedent from test.support import (captured_stderr, check_impl_detail, cpython_only, gc_collect, no_tracing, script_helper, - SuppressCrashReport) + SuppressCrashReport, + force_not_colorized) from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink from test.support.warnings_helper import check_warnings @@ -41,6 +42,7 @@ class BrokenStrException(Exception): # XXX This is not really enough, each *operation* should be tested! + class ExceptionTests(unittest.TestCase): def raise_catch(self, exc, excname): @@ -1994,6 +1996,7 @@ class AssertionErrorTests(unittest.TestCase): _rc, _out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN) return err.decode('utf-8').splitlines() + @force_not_colorized def test_assertion_error_location(self): cases = [ ('assert None', @@ -2070,6 +2073,7 @@ class AssertionErrorTests(unittest.TestCase): result = self.write_source(source) self.assertEqual(result[-3:], expected) + @force_not_colorized def test_multiline_not_highlighted(self): cases = [ (""" @@ -2102,6 +2106,7 @@ class AssertionErrorTests(unittest.TestCase): class SyntaxErrorTests(unittest.TestCase): + @force_not_colorized def test_range_of_offsets(self): cases = [ # Basic range from 2->7 diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 0039fa4..719c1c7 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -12,6 +12,7 @@ from test.support import import_helper _interpreters = import_helper.import_module('_interpreters') from test.support import Py_GIL_DISABLED from test.support import interpreters +from test.support import force_not_colorized from test.support.interpreters import ( InterpreterError, InterpreterNotFoundError, ExecutionFailed, ) @@ -735,6 +736,7 @@ class TestInterpreterExec(TestBase): with self.assertRaises(ExecutionFailed): interp.exec('raise Exception') + @force_not_colorized def test_display_preserved_exception(self): tempdir = self.temp_dir() modfile = self.make_module('spam', tempdir, text=""" diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index ab26bf5..14ec51e 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -16,6 +16,7 @@ from test.support import os_helper from test.support.script_helper import assert_python_ok, assert_python_failure from test.support import threading_helper from test.support import import_helper +from test.support import force_not_colorized try: from test.support import interpreters except ImportError: @@ -145,6 +146,7 @@ class ActiveExceptionTests(unittest.TestCase): class ExceptHookTest(unittest.TestCase): + @force_not_colorized def test_original_excepthook(self): try: raise ValueError(42) @@ -156,6 +158,7 @@ class ExceptHookTest(unittest.TestCase): self.assertRaises(TypeError, sys.__excepthook__) + @force_not_colorized def test_excepthook_bytes_filename(self): # bpo-37467: sys.excepthook() must not crash if a filename # is a bytes string @@ -793,6 +796,7 @@ class SysModuleTest(unittest.TestCase): def test_clear_type_cache(self): sys._clear_type_cache() + @force_not_colorized @support.requires_subprocess() def test_ioencoding(self): env = dict(os.environ) @@ -1108,6 +1112,7 @@ class SysModuleTest(unittest.TestCase): self.assertIsInstance(level, int) self.assertGreater(level, 0) + @force_not_colorized @support.requires_subprocess() def test_sys_tracebacklimit(self): code = """if 1: diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index a712ed1..362a3f9 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -7,6 +7,7 @@ from test.support import threading_helper, requires_subprocess from test.support import verbose, cpython_only, os_helper from test.support.import_helper import import_module from test.support.script_helper import assert_python_ok, assert_python_failure +from test.support import force_not_colorized import random import sys @@ -1793,6 +1794,7 @@ class ExceptHookTests(BaseTestCase): restore_default_excepthook(self) super().setUp() + @force_not_colorized def test_excepthook(self): with support.captured_output("stderr") as stderr: thread = ThreadRunFail(name="excepthook thread") @@ -1806,6 +1808,7 @@ class ExceptHookTests(BaseTestCase): self.assertIn('ValueError: run failed', stderr) @support.cpython_only + @force_not_colorized def test_excepthook_thread_None(self): # threading.excepthook called with thread=None: log the thread # identifier in this case. diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index dd9b185..1961193 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -21,6 +21,7 @@ from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ, 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 import json import textwrap @@ -39,6 +40,13 @@ test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti']) LEVENSHTEIN_DATA_FILE = Path(__file__).parent / 'levenshtein_examples.json' +ORIGINAL_CAN_COLORIZE = traceback._can_colorize + +def setUpModule(): + traceback._can_colorize = lambda: False + +def tearDownModule(): + traceback._can_colorize = ORIGINAL_CAN_COLORIZE class TracebackCases(unittest.TestCase): # For now, a very minimal set of tests. I want to be sure that @@ -124,6 +132,7 @@ class TracebackCases(unittest.TestCase): self.assertEqual(len(err), 3) self.assertEqual(err[1].strip(), "bad syntax") + @force_not_colorized def test_no_caret_with_no_debug_ranges_flag(self): # Make sure that if `-X no_debug_ranges` is used, there are no carets # in the traceback. @@ -401,7 +410,7 @@ class TracebackCases(unittest.TestCase): """.format(firstlines, message)) process = subprocess.Popen([sys.executable, TESTFN], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env={}) stdout, stderr = process.communicate() stdout = stdout.decode(output_encoding).splitlines() finally: @@ -4354,13 +4363,18 @@ class TestColorizedTraceback(unittest.TestCase): f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}'] self.assertEqual(actual, expected) + @force_not_colorized def test_colorized_detection_checks_for_environment_variables(self): if sys.platform == "win32": virtual_patching = unittest.mock.patch("nt._supports_virtual_terminal", return_value=True) else: virtual_patching = contextlib.nullcontext() with virtual_patching: - with unittest.mock.patch("os.isatty") as isatty_mock: + + flags = unittest.mock.MagicMock(ignore_environment=False) + with (unittest.mock.patch("os.isatty") as isatty_mock, + unittest.mock.patch("sys.flags", flags), + unittest.mock.patch("traceback._can_colorize", ORIGINAL_CAN_COLORIZE)): isatty_mock.return_value = True with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): self.assertEqual(traceback._can_colorize(), False) @@ -4379,7 +4393,8 @@ class TestColorizedTraceback(unittest.TestCase): with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): self.assertEqual(traceback._can_colorize(), False) isatty_mock.return_value = False - self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {}): + self.assertEqual(traceback._can_colorize(), False) if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py index bea1245..f685430 100644 --- a/Lib/test/test_tracemalloc.py +++ b/Lib/test/test_tracemalloc.py @@ -942,7 +942,7 @@ class TestCommandLine(unittest.TestCase): with support.SuppressCrashReport(): ok, stdout, stderr = assert_python_failure( '-c', 'pass', - PYTHONTRACEMALLOC=str(nframe)) + PYTHONTRACEMALLOC=str(nframe), __cleanenv=True) if b'ValueError: the number of frames must be in range' in stderr: return diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index b768631..4416ed0 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -12,6 +12,7 @@ from test import support from test.support import import_helper from test.support import os_helper from test.support import warnings_helper +from test.support import force_not_colorized from test.support.script_helper import assert_python_ok, assert_python_failure from test.test_warnings.data import package_helper @@ -1239,6 +1240,7 @@ class EnvironmentVariableTests(BaseTest): self.assertEqual(stdout, b"['ignore::DeprecationWarning', 'ignore::UnicodeWarning']") + @force_not_colorized def test_envvar_and_command_line(self): rc, stdout, stderr = assert_python_ok("-Wignore::UnicodeWarning", "-c", "import sys; sys.stdout.write(str(sys.warnoptions))", @@ -1247,6 +1249,7 @@ class EnvironmentVariableTests(BaseTest): self.assertEqual(stdout, b"['ignore::DeprecationWarning', 'ignore::UnicodeWarning']") + @force_not_colorized def test_conflicting_envvar_and_command_line(self): rc, stdout, stderr = assert_python_failure("-Werror::DeprecationWarning", "-c", "import sys, warnings; sys.stdout.write(str(sys.warnoptions)); " diff --git a/Lib/traceback.py b/Lib/traceback.py index 054def5..fccec0c 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -141,24 +141,30 @@ def _can_colorize(): return False except (ImportError, AttributeError): return False - - if os.environ.get("PYTHON_COLORS") == "0": - return False - if os.environ.get("PYTHON_COLORS") == "1": - return True - if "NO_COLOR" in os.environ: - return False + if not sys.flags.ignore_environment: + if os.environ.get("PYTHON_COLORS") == "0": + return False + if os.environ.get("PYTHON_COLORS") == "1": + return True + if "NO_COLOR" in os.environ: + return False if not _COLORIZE: return False - if "FORCE_COLOR" in os.environ: - return True - if os.environ.get("TERM") == "dumb": + if not sys.flags.ignore_environment: + if "FORCE_COLOR" in os.environ: + return True + if os.environ.get("TERM") == "dumb": + return False + + if not hasattr(sys.stderr, "fileno"): return False + try: return os.isatty(sys.stderr.fileno()) except io.UnsupportedOperation: return sys.stderr.isatty() + def _print_exception_bltin(exc, /): file = sys.stderr if sys.stderr is not None else sys.__stderr__ colorize = _can_colorize() |