From e08e26c5d6a5cc77875832e74cf45536e5a83fbb Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Mon, 8 Apr 2024 15:35:53 +0200 Subject: [PATCH 1/7] gh-112730: Make the test suite relient to color-activation environment variables --- Lib/doctest.py | 10 +++++++++- Lib/idlelib/idle_test/test_run.py | 3 +++ Lib/test/support/__init__.py | 22 +++++++++++++++++++++- Lib/test/test_capi/test_misc.py | 4 ++++ Lib/test/test_cmd_line.py | 2 ++ Lib/test/test_cmd_line_script.py | 12 +++++++++++- Lib/test/test_compileall.py | 2 ++ Lib/test/test_eof.py | 2 ++ Lib/test/test_exceptions.py | 10 +++++++++- Lib/test/test_inspect/test_inspect.py | 2 ++ Lib/test/test_interpreters/test_api.py | 2 ++ Lib/test/test_regrtest.py | 2 ++ Lib/test/test_repl.py | 3 +++ Lib/test/test_runpy.py | 10 +++++++++- Lib/test/test_sys.py | 5 +++++ Lib/test/test_threading.py | 3 +++ Lib/test/test_traceback.py | 19 +++++++++++++++---- Lib/test/test_tracemalloc.py | 4 +++- Lib/test/test_unicodedata.py | 3 ++- Lib/test/test_warnings/__init__.py | 3 +++ Lib/traceback.py | 11 ++++++----- 21 files changed, 118 insertions(+), 16 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index fc0da590018b40..bf59da7c3d3274 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1540,7 +1540,11 @@ def out(s): # 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: @@ -1549,6 +1553,10 @@ def out(s): 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 a38e43dcb9d1c4..83ecbffa2a197e 100644 --- a/Lib/idlelib/idle_test/test_run.py +++ b/Lib/idlelib/idle_test/test_run.py @@ -8,6 +8,7 @@ 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 @@ def __eq__(self, other): "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 @@ def test_get_message(self): 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 2be9cd099a68d6..bda416149f6ccc 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -58,7 +58,8 @@ "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT", "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "Py_C_RECURSION_LIMIT", "skip_on_s390x", - "without_optimizer", + "without_optimizer", "set_not_colorized_globally", "reset_colorized_globally", + "force_not_colorized" ] @@ -2555,3 +2556,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 \ No newline at end of file diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 2f2bf03749f834..fec3fb144b667e 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -25,6 +25,7 @@ from test.support import import_helper from test.support import threading_helper from test.support import warnings_helper +from test.support import force_not_colorized from test.support import requires_limited_api from test.support.script_helper import assert_python_failure, assert_python_ok, run_python_until_end try: @@ -205,6 +206,7 @@ def test_c_type_with_ipow(self): self.assertEqual(o.__ipow__(1), (1, None)) self.assertEqual(o.__ipow__(2, 2), (2, 2)) + @force_not_colorized def test_return_null_without_error(self): # Issue #23571: A function must not return NULL without setting an # error @@ -234,6 +236,7 @@ def test_return_null_without_error(self): 'return_null_without_error.* ' 'returned NULL without setting an exception') + @force_not_colorized def test_return_result_with_error(self): # Issue #23571: A function must not return a result with an error set if support.Py_DEBUG: @@ -268,6 +271,7 @@ def test_return_result_with_error(self): 'return_result_with_error.* ' 'returned a result with an exception set') + @force_not_colorized def test_getitem_with_error(self): # Test _Py_CheckSlotResult(). Raise an exception and then calls # PyObject_GetItem(): check that the assertion catches the bug. diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index fb832aed3152ff..9624d35d0c3948 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -10,6 +10,7 @@ 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 @@ def test_sys_flags_not_set(self): 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_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 3a5a8abf81e43d..7c90c0ab58a95b 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -14,7 +14,7 @@ import textwrap from test import support -from test.support import import_helper, is_apple, os_helper +from test.support import import_helper, is_apple, os_helper, force_not_colorized from test.support.script_helper import ( make_pkg, make_script, make_zip_pkg, make_zip_script, assert_python_ok, assert_python_failure, spawn_python, kill_python) @@ -195,6 +195,7 @@ def check_repl_stdout_flush(self, separate_stderr=False): p.stdin.flush() self.assertEqual(b'foo', p.stdout.readline().strip()) + @force_not_colorized def check_repl_stderr_flush(self, separate_stderr=False): with self.interactive_python(separate_stderr) as p: p.stdin.write(b"1/0\n") @@ -537,6 +538,7 @@ def test_dash_m_main_traceback(self): self.assertIn(b'Exception in __main__ module', err) self.assertIn(b'Traceback', err) + @force_not_colorized def test_pep_409_verbiage(self): # Make sure PEP 409 syntax properly suppresses # the context of an exception @@ -602,6 +604,7 @@ def test_issue20500_exit_with_exception_value(self): text = stderr.decode('ascii') self.assertEqual(text.rstrip(), "some text") + @force_not_colorized def test_syntaxerror_unindented_caret_position(self): script = "1 + 1 = 2\n" with os_helper.temp_dir() as script_dir: @@ -611,6 +614,7 @@ def test_syntaxerror_unindented_caret_position(self): # Confirm that the caret is located under the '=' sign self.assertIn("\n ^^^^^\n", text) + @force_not_colorized def test_syntaxerror_indented_caret_position(self): script = textwrap.dedent("""\ if True: @@ -634,6 +638,7 @@ def test_syntaxerror_indented_caret_position(self): self.assertNotIn("\f", text) self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text) + @force_not_colorized def test_syntaxerror_multi_line_fstring(self): script = 'foo = f"""{}\nfoo"""\n' with os_helper.temp_dir() as script_dir: @@ -648,6 +653,7 @@ def test_syntaxerror_multi_line_fstring(self): ], ) + @force_not_colorized def test_syntaxerror_invalid_escape_sequence_multi_line(self): script = 'foo = """\\q"""\n' with os_helper.temp_dir() as script_dir: @@ -663,6 +669,7 @@ def test_syntaxerror_invalid_escape_sequence_multi_line(self): ], ) + @force_not_colorized def test_syntaxerror_null_bytes(self): script = "x = '\0' nothing to see here\n';import os;os.system('echo pwnd')\n" with os_helper.temp_dir() as script_dir: @@ -675,6 +682,7 @@ def test_syntaxerror_null_bytes(self): ], ) + @force_not_colorized def test_syntaxerror_null_bytes_in_multiline_string(self): scripts = ["\n'''\nmultilinestring\0\n'''", "\nf'''\nmultilinestring\0\n'''"] # Both normal and f-strings with os_helper.temp_dir() as script_dir: @@ -688,6 +696,7 @@ def test_syntaxerror_null_bytes_in_multiline_string(self): ] ) + @force_not_colorized def test_source_lines_are_shown_when_running_source(self): _, _, stderr = assert_python_failure("-c", "1/0") expected_lines = [ @@ -698,6 +707,7 @@ def test_source_lines_are_shown_when_running_source(self): b'ZeroDivisionError: division by zero'] self.assertEqual(stderr.splitlines(), expected_lines) + @force_not_colorized def test_syntaxerror_does_not_crash(self): script = "nonlocal x\n" with os_helper.temp_dir() as script_dir: diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py index 14c2af19e3eb28..65ece29e935a34 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -29,6 +29,7 @@ from test import support from test.support import os_helper from test.support import script_helper +from test.support import force_not_colorized from test.test_py_compile import without_source_date_epoch from test.test_py_compile import SourceDateEpochTestMeta @@ -760,6 +761,7 @@ def test_d_compile_error(self): rc, out, err = self.assertRunNotOK('-q', '-d', 'dinsdale', self.pkgdir) self.assertRegex(out, b'File "dinsdale') + @force_not_colorized def test_d_runtime_error(self): bazfn = script_helper.make_script(self.pkgdir, 'baz', 'raise Exception') self.assertRunOK('-q', '-d', 'dinsdale', self.pkgdir) diff --git a/Lib/test/test_eof.py b/Lib/test/test_eof.py index be4fd73bfdc36b..caae8ac238ca38 100644 --- a/Lib/test/test_eof.py +++ b/Lib/test/test_eof.py @@ -5,6 +5,7 @@ from test.support import os_helper from test.support import script_helper from test.support import warnings_helper +from test.support import force_not_colorized import unittest class EOFTestCase(unittest.TestCase): @@ -58,6 +59,7 @@ def test_line_continuation_EOF(self): self.assertEqual(str(excinfo.exception), expect) @unittest.skipIf(not sys.executable, "sys.executable required") + @force_not_colorized def test_line_continuation_EOF_from_file_bpo2180(self): """Ensure tok_nextc() does not add too many ending newlines.""" with os_helper.temp_dir() as temp_dir: diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 6ad6acc61563e5..6aa7ddceb19441 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -12,7 +12,8 @@ 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 @@ def __str__(self): # XXX This is not really enough, each *operation* should be tested! + class ExceptionTests(unittest.TestCase): def raise_catch(self, exc, excname): @@ -1438,6 +1440,7 @@ def gen(): @cpython_only @unittest.skipIf(_testcapi is None, "requires _testcapi") + @force_not_colorized def test_recursion_normalizing_infinite_exception(self): # Issue #30697. Test that a RecursionError is raised when # maximum recursion depth has been exceeded when creating @@ -1993,6 +1996,7 @@ def write_source(self, source): _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', @@ -2069,6 +2073,7 @@ def test_assertion_error_location(self): result = self.write_source(source) self.assertEqual(result[-3:], expected) + @force_not_colorized def test_multiline_not_highlighted(self): cases = [ (""" @@ -2101,6 +2106,7 @@ def test_multiline_not_highlighted(self): class SyntaxErrorTests(unittest.TestCase): + @force_not_colorized def test_range_of_offsets(self): cases = [ # Basic range from 2->7 @@ -2191,6 +2197,7 @@ def test_range_of_offsets(self): self.assertIn(expected, err.getvalue()) the_exception = exc + @force_not_colorized def test_encodings(self): source = ( '# -*- coding: cp437 -*-\n' @@ -2220,6 +2227,7 @@ def test_encodings(self): finally: unlink(TESTFN) + @force_not_colorized def test_non_utf8(self): # Check non utf-8 characters try: diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 6494842c217662..6b9a6fe42365e5 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -38,6 +38,7 @@ from test.support.os_helper import TESTFN, temp_cwd from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python from test.support import has_subprocess_support, SuppressCrashReport +from test.support import force_not_colorized from test import support from test.test_inspect import inspect_fodder as mod @@ -816,6 +817,7 @@ def test_getsource_on_code_object(self): self.assertSourceEqual(mod.eggs.__code__, 12, 18) class TestGetsourceInteractive(unittest.TestCase): + @force_not_colorized def test_getclasses_interactive(self): # bpo-44648: simulate a REPL session; # there is no `__file__` in the __main__ module diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index a326b39fd234c7..460ab217a4523d 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -10,6 +10,7 @@ # Raise SkipTest if subinterpreters not supported. _interpreters = import_helper.import_module('_xxsubinterpreters') from test.support import interpreters +from test.support import force_not_colorized from test.support.interpreters import InterpreterNotFoundError from .utils import _captured_script, _run_output, _running, TestBase @@ -533,6 +534,7 @@ def test_failure(self): with self.assertRaises(interpreters.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_regrtest.py b/Lib/test/test_regrtest.py index d222b3803fdba7..be6e593a4e067f 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -23,6 +23,7 @@ import unittest from test import support from test.support import os_helper, without_optimizer +from test.support import force_not_colorized from test.libregrtest import cmdline from test.libregrtest import main from test.libregrtest import setup @@ -1835,6 +1836,7 @@ def test_unraisable_exc(self): self.assertIn("Warning -- Unraisable exception", output) self.assertIn("Exception: weakref callback bug", output) + @force_not_colorized def test_threading_excepthook(self): # --fail-env-changed must catch uncaught thread exception. # The exception must be displayed even if sys.stderr is redirected. diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 457279a4db687d..a51777f6c8dec1 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -7,6 +7,7 @@ from textwrap import dedent from test import support from test.support import cpython_only, has_subprocess_support, SuppressCrashReport +from test.support import force_not_colorized from test.support.script_helper import kill_python from test.support.import_helper import import_module @@ -15,6 +16,7 @@ raise unittest.SkipTest("test module requires subprocess") +@force_not_colorized def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): """Run the Python REPL with the given arguments. @@ -43,6 +45,7 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): stdout=stdout, stderr=stderr, **kw) +@force_not_colorized def run_on_interactive_mode(source): """Spawn a new Python interpreter, pass the given input source code from the stdin and return the diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 9d76764c75be3e..b6e0c66b44605f 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -13,7 +13,8 @@ import unittest import warnings from test.support import (infinite_recursion, no_tracing, verbose, - requires_subprocess, requires_resource) + requires_subprocess, requires_resource, + force_not_colorized) from test.support.import_helper import forget, make_legacy_pyc, unload from test.support.os_helper import create_empty_file, temp_dir from test.support.script_helper import make_script, make_zip_script @@ -786,6 +787,7 @@ def run(self, *args, **kwargs): super().run(*args, **kwargs) @requires_subprocess() + @force_not_colorized def assertSigInt(self, cmd, *args, **kwargs): # Use -E to ignore PYTHONSAFEPATH cmd = [sys.executable, '-E', *cmd] @@ -796,6 +798,7 @@ def assertSigInt(self, cmd, *args, **kwargs): def test_pymain_run_file(self): self.assertSigInt([self.ham]) + @force_not_colorized def test_pymain_run_file_runpy_run_module(self): tmp = self.ham.parent run_module = tmp / "run_module.py" @@ -809,6 +812,7 @@ def test_pymain_run_file_runpy_run_module(self): ) self.assertSigInt([run_module], cwd=tmp) + @force_not_colorized def test_pymain_run_file_runpy_run_module_as_main(self): tmp = self.ham.parent run_module_as_main = tmp / "run_module_as_main.py" @@ -822,18 +826,22 @@ def test_pymain_run_file_runpy_run_module_as_main(self): ) self.assertSigInt([run_module_as_main], cwd=tmp) + @force_not_colorized def test_pymain_run_command_run_module(self): self.assertSigInt( ["-c", "import runpy; runpy.run_module('ham')"], cwd=self.ham.parent, ) + @force_not_colorized def test_pymain_run_command(self): self.assertSigInt(["-c", "import ham"], cwd=self.ham.parent) + @force_not_colorized def test_pymain_run_stdin(self): self.assertSigInt([], input="import ham", cwd=self.ham.parent) + @force_not_colorized def test_pymain_run_module(self): ham = self.ham self.assertSigInt(["-m", ham.stem], cwd=ham.parent) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 6a66df4e897e3f..bd0a040d0f6836 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -16,6 +16,7 @@ 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 @@ def f(): class ExceptHookTest(unittest.TestCase): + @force_not_colorized def test_original_excepthook(self): try: raise ValueError(42) @@ -156,6 +158,7 @@ def test_original_excepthook(self): 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 @@ def test_sys_getwindowsversion_no_instantiation(self): 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 @@ def test_getandroidapilevel(self): 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 a7701fa285aee2..f985f7060ef2c3 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -7,6 +7,7 @@ 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 @@ -1792,6 +1793,7 @@ def setUp(self): 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") @@ -1805,6 +1807,7 @@ def test_excepthook(self): 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 dd9b1850adf086..7c98de030585a6 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -21,6 +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 import json import textwrap @@ -39,6 +40,13 @@ 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 @@ def test_nocaret(self): 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. @@ -387,7 +396,7 @@ def test_encoded_file(self): process = subprocess.Popen([sys.executable, "-c", "import sys; print(sys.stdout.encoding)"], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) + stderr=subprocess.STDOUT, env={}) stdout, stderr = process.communicate() output_encoding = str(stdout, 'ascii').splitlines()[0] @@ -401,7 +410,7 @@ def do_test(firstlines, message, charset, lineno): """.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: @@ -4360,7 +4369,8 @@ def test_colorized_detection_checks_for_environment_variables(self): else: virtual_patching = contextlib.nullcontext() with virtual_patching: - with unittest.mock.patch("os.isatty") as isatty_mock: + with (unittest.mock.patch("os.isatty") as isatty_mock, + 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 +4389,8 @@ def test_colorized_detection_checks_for_environment_variables(self): 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 bea124521032d1..3d6e7ceb3c5d82 100644 --- a/Lib/test/test_tracemalloc.py +++ b/Lib/test/test_tracemalloc.py @@ -8,6 +8,7 @@ interpreter_requires_environment) from test import support from test.support import os_helper +from test.support import force_not_colorized try: import _testcapi @@ -942,7 +943,7 @@ def check_env_var_invalid(self, nframe): 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 @@ -979,6 +980,7 @@ def check_sys_xoptions_invalid(self, nframe): return self.fail(f"unexpected output: {stderr!a}") + @force_not_colorized def test_sys_xoptions_invalid(self): for nframe in INVALID_NFRAME: with self.subTest(nframe=nframe): diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py index d3bf4ea7c7d437..7df0e6a36e11fd 100644 --- a/Lib/test/test_unicodedata.py +++ b/Lib/test/test_unicodedata.py @@ -12,7 +12,7 @@ import unicodedata import unittest from test.support import (open_urlresource, requires_resource, script_helper, - cpython_only, check_disallow_instantiation) + cpython_only, check_disallow_instantiation, force_not_colorized) class UnicodeMethodsTest(unittest.TestCase): @@ -277,6 +277,7 @@ def test_disallow_instantiation(self): # Ensure that the type disallows instantiation (bpo-43916) check_disallow_instantiation(self, unicodedata.UCD) + @force_not_colorized def test_failed_import_during_compiling(self): # Issue 4367 # Decoding \N escapes requires the unicodedata module. If it can't be diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 50b0f3fff04c57..e4e1981f994739 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -12,6 +12,7 @@ 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 @@ def test_comma_separated_warnings(self): 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 @@ def test_envvar_and_command_line(self): 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 d27c7a726d2bb6..19dd8250b7f940 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -141,11 +141,12 @@ 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 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: From ee009031a53af944c25b025bdbc8f7bc90c3e9c3 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Tue, 9 Apr 2024 11:15:54 +0100 Subject: [PATCH 2/7] Force color in CI --- .github/workflows/reusable-ubuntu.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index ee64fe62a0bd0a..c2e3d93cce1f7a 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -16,6 +16,7 @@ jobs: env: OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 + FORCE_COLOR: 1 steps: - uses: actions/checkout@v4 - name: Register gcc problem matcher From 081aaed6f69881d3b8caecb10aa7a2d0615bc5ac Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Tue, 9 Apr 2024 14:01:47 +0100 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/reusable-ubuntu.yml | 2 +- Lib/test/support/__init__.py | 4 ++-- Lib/test/test_traceback.py | 2 +- Lib/traceback.py | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index c2e3d93cce1f7a..e6fbaaf74c5a4b 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -14,9 +14,9 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-20.04 env: + FORCE_COLOR: 1 OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 - FORCE_COLOR: 1 steps: - uses: actions/checkout@v4 - name: Register gcc problem matcher diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index bda416149f6ccc..6b69636e710805 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -58,7 +58,7 @@ "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT", "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "Py_C_RECURSION_LIMIT", "skip_on_s390x", - "without_optimizer", "set_not_colorized_globally", "reset_colorized_globally", + "without_optimizer", "reset_colorized_globally", "force_not_colorized" ] @@ -2574,4 +2574,4 @@ def wrapper(*args, **kwargs): for key, value in variables.items(): if value is not None: os.environ[key] = value - return wrapper \ No newline at end of file + return wrapper diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 7c98de030585a6..1534ebeada674d 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4369,7 +4369,7 @@ def test_colorized_detection_checks_for_environment_variables(self): else: virtual_patching = contextlib.nullcontext() with virtual_patching: - with (unittest.mock.patch("os.isatty") as isatty_mock, + with (unittest.mock.patch("os.isatty") as isatty_mock, unittest.mock.patch("traceback._can_colorize", ORIGINAL_CAN_COLORIZE)): isatty_mock.return_value = True with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): diff --git a/Lib/traceback.py b/Lib/traceback.py index 19dd8250b7f940..b5070912207998 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -141,7 +141,6 @@ def _can_colorize(): return False except (ImportError, AttributeError): return False - if not sys.flags.ignore_environment: if os.environ.get("PYTHON_COLORS") == "0": return False From ee4966ad5319fc88ee408ccf4e9d078ca777881e Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 10 Apr 2024 11:19:03 +0100 Subject: [PATCH 4/7] Update Lib/test/support/__init__.py Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Lib/test/support/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 6b69636e710805..3c0b6789643f72 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -58,7 +58,7 @@ "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT", "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "Py_C_RECURSION_LIMIT", "skip_on_s390x", - "without_optimizer", "reset_colorized_globally", + "without_optimizer", "force_not_colorized" ] From 15c840a3b124481e923fcb42f8e026913fb291f5 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 10 Apr 2024 11:41:14 +0100 Subject: [PATCH 5/7] Deactivate on -E --- Lib/test/test_capi/test_misc.py | 4 ---- Lib/test/test_cmd_line_script.py | 12 +----------- Lib/test/test_compileall.py | 2 -- Lib/test/test_eof.py | 2 -- Lib/test/test_exceptions.py | 3 --- Lib/test/test_inspect/test_inspect.py | 2 -- Lib/test/test_regrtest.py | 2 -- Lib/test/test_repl.py | 3 --- Lib/test/test_runpy.py | 10 +--------- Lib/test/test_traceback.py | 2 +- Lib/test/test_tracemalloc.py | 2 -- Lib/test/test_unicodedata.py | 3 +-- Lib/traceback.py | 13 +++++++------ 13 files changed, 11 insertions(+), 49 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index fec3fb144b667e..2f2bf03749f834 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -25,7 +25,6 @@ from test.support import import_helper from test.support import threading_helper from test.support import warnings_helper -from test.support import force_not_colorized from test.support import requires_limited_api from test.support.script_helper import assert_python_failure, assert_python_ok, run_python_until_end try: @@ -206,7 +205,6 @@ def test_c_type_with_ipow(self): self.assertEqual(o.__ipow__(1), (1, None)) self.assertEqual(o.__ipow__(2, 2), (2, 2)) - @force_not_colorized def test_return_null_without_error(self): # Issue #23571: A function must not return NULL without setting an # error @@ -236,7 +234,6 @@ def test_return_null_without_error(self): 'return_null_without_error.* ' 'returned NULL without setting an exception') - @force_not_colorized def test_return_result_with_error(self): # Issue #23571: A function must not return a result with an error set if support.Py_DEBUG: @@ -271,7 +268,6 @@ def test_return_result_with_error(self): 'return_result_with_error.* ' 'returned a result with an exception set') - @force_not_colorized def test_getitem_with_error(self): # Test _Py_CheckSlotResult(). Raise an exception and then calls # PyObject_GetItem(): check that the assertion catches the bug. diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 7c90c0ab58a95b..3a5a8abf81e43d 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -14,7 +14,7 @@ import textwrap from test import support -from test.support import import_helper, is_apple, os_helper, force_not_colorized +from test.support import import_helper, is_apple, os_helper from test.support.script_helper import ( make_pkg, make_script, make_zip_pkg, make_zip_script, assert_python_ok, assert_python_failure, spawn_python, kill_python) @@ -195,7 +195,6 @@ def check_repl_stdout_flush(self, separate_stderr=False): p.stdin.flush() self.assertEqual(b'foo', p.stdout.readline().strip()) - @force_not_colorized def check_repl_stderr_flush(self, separate_stderr=False): with self.interactive_python(separate_stderr) as p: p.stdin.write(b"1/0\n") @@ -538,7 +537,6 @@ def test_dash_m_main_traceback(self): self.assertIn(b'Exception in __main__ module', err) self.assertIn(b'Traceback', err) - @force_not_colorized def test_pep_409_verbiage(self): # Make sure PEP 409 syntax properly suppresses # the context of an exception @@ -604,7 +602,6 @@ def test_issue20500_exit_with_exception_value(self): text = stderr.decode('ascii') self.assertEqual(text.rstrip(), "some text") - @force_not_colorized def test_syntaxerror_unindented_caret_position(self): script = "1 + 1 = 2\n" with os_helper.temp_dir() as script_dir: @@ -614,7 +611,6 @@ def test_syntaxerror_unindented_caret_position(self): # Confirm that the caret is located under the '=' sign self.assertIn("\n ^^^^^\n", text) - @force_not_colorized def test_syntaxerror_indented_caret_position(self): script = textwrap.dedent("""\ if True: @@ -638,7 +634,6 @@ def test_syntaxerror_indented_caret_position(self): self.assertNotIn("\f", text) self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text) - @force_not_colorized def test_syntaxerror_multi_line_fstring(self): script = 'foo = f"""{}\nfoo"""\n' with os_helper.temp_dir() as script_dir: @@ -653,7 +648,6 @@ def test_syntaxerror_multi_line_fstring(self): ], ) - @force_not_colorized def test_syntaxerror_invalid_escape_sequence_multi_line(self): script = 'foo = """\\q"""\n' with os_helper.temp_dir() as script_dir: @@ -669,7 +663,6 @@ def test_syntaxerror_invalid_escape_sequence_multi_line(self): ], ) - @force_not_colorized def test_syntaxerror_null_bytes(self): script = "x = '\0' nothing to see here\n';import os;os.system('echo pwnd')\n" with os_helper.temp_dir() as script_dir: @@ -682,7 +675,6 @@ def test_syntaxerror_null_bytes(self): ], ) - @force_not_colorized def test_syntaxerror_null_bytes_in_multiline_string(self): scripts = ["\n'''\nmultilinestring\0\n'''", "\nf'''\nmultilinestring\0\n'''"] # Both normal and f-strings with os_helper.temp_dir() as script_dir: @@ -696,7 +688,6 @@ def test_syntaxerror_null_bytes_in_multiline_string(self): ] ) - @force_not_colorized def test_source_lines_are_shown_when_running_source(self): _, _, stderr = assert_python_failure("-c", "1/0") expected_lines = [ @@ -707,7 +698,6 @@ def test_source_lines_are_shown_when_running_source(self): b'ZeroDivisionError: division by zero'] self.assertEqual(stderr.splitlines(), expected_lines) - @force_not_colorized def test_syntaxerror_does_not_crash(self): script = "nonlocal x\n" with os_helper.temp_dir() as script_dir: diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py index 65ece29e935a34..14c2af19e3eb28 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -29,7 +29,6 @@ from test import support from test.support import os_helper from test.support import script_helper -from test.support import force_not_colorized from test.test_py_compile import without_source_date_epoch from test.test_py_compile import SourceDateEpochTestMeta @@ -761,7 +760,6 @@ def test_d_compile_error(self): rc, out, err = self.assertRunNotOK('-q', '-d', 'dinsdale', self.pkgdir) self.assertRegex(out, b'File "dinsdale') - @force_not_colorized def test_d_runtime_error(self): bazfn = script_helper.make_script(self.pkgdir, 'baz', 'raise Exception') self.assertRunOK('-q', '-d', 'dinsdale', self.pkgdir) diff --git a/Lib/test/test_eof.py b/Lib/test/test_eof.py index caae8ac238ca38..be4fd73bfdc36b 100644 --- a/Lib/test/test_eof.py +++ b/Lib/test/test_eof.py @@ -5,7 +5,6 @@ from test.support import os_helper from test.support import script_helper from test.support import warnings_helper -from test.support import force_not_colorized import unittest class EOFTestCase(unittest.TestCase): @@ -59,7 +58,6 @@ def test_line_continuation_EOF(self): self.assertEqual(str(excinfo.exception), expect) @unittest.skipIf(not sys.executable, "sys.executable required") - @force_not_colorized def test_line_continuation_EOF_from_file_bpo2180(self): """Ensure tok_nextc() does not add too many ending newlines.""" with os_helper.temp_dir() as temp_dir: diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 6aa7ddceb19441..5217deb10ea0a5 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1440,7 +1440,6 @@ def gen(): @cpython_only @unittest.skipIf(_testcapi is None, "requires _testcapi") - @force_not_colorized def test_recursion_normalizing_infinite_exception(self): # Issue #30697. Test that a RecursionError is raised when # maximum recursion depth has been exceeded when creating @@ -2197,7 +2196,6 @@ def test_range_of_offsets(self): self.assertIn(expected, err.getvalue()) the_exception = exc - @force_not_colorized def test_encodings(self): source = ( '# -*- coding: cp437 -*-\n' @@ -2227,7 +2225,6 @@ def test_encodings(self): finally: unlink(TESTFN) - @force_not_colorized def test_non_utf8(self): # Check non utf-8 characters try: diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 6b9a6fe42365e5..6494842c217662 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -38,7 +38,6 @@ from test.support.os_helper import TESTFN, temp_cwd from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python from test.support import has_subprocess_support, SuppressCrashReport -from test.support import force_not_colorized from test import support from test.test_inspect import inspect_fodder as mod @@ -817,7 +816,6 @@ def test_getsource_on_code_object(self): self.assertSourceEqual(mod.eggs.__code__, 12, 18) class TestGetsourceInteractive(unittest.TestCase): - @force_not_colorized def test_getclasses_interactive(self): # bpo-44648: simulate a REPL session; # there is no `__file__` in the __main__ module diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index be6e593a4e067f..d222b3803fdba7 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -23,7 +23,6 @@ import unittest from test import support from test.support import os_helper, without_optimizer -from test.support import force_not_colorized from test.libregrtest import cmdline from test.libregrtest import main from test.libregrtest import setup @@ -1836,7 +1835,6 @@ def test_unraisable_exc(self): self.assertIn("Warning -- Unraisable exception", output) self.assertIn("Exception: weakref callback bug", output) - @force_not_colorized def test_threading_excepthook(self): # --fail-env-changed must catch uncaught thread exception. # The exception must be displayed even if sys.stderr is redirected. diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index a51777f6c8dec1..457279a4db687d 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -7,7 +7,6 @@ from textwrap import dedent from test import support from test.support import cpython_only, has_subprocess_support, SuppressCrashReport -from test.support import force_not_colorized from test.support.script_helper import kill_python from test.support.import_helper import import_module @@ -16,7 +15,6 @@ raise unittest.SkipTest("test module requires subprocess") -@force_not_colorized def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): """Run the Python REPL with the given arguments. @@ -45,7 +43,6 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): stdout=stdout, stderr=stderr, **kw) -@force_not_colorized def run_on_interactive_mode(source): """Spawn a new Python interpreter, pass the given input source code from the stdin and return the diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index b6e0c66b44605f..9d76764c75be3e 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -13,8 +13,7 @@ import unittest import warnings from test.support import (infinite_recursion, no_tracing, verbose, - requires_subprocess, requires_resource, - force_not_colorized) + requires_subprocess, requires_resource) from test.support.import_helper import forget, make_legacy_pyc, unload from test.support.os_helper import create_empty_file, temp_dir from test.support.script_helper import make_script, make_zip_script @@ -787,7 +786,6 @@ def run(self, *args, **kwargs): super().run(*args, **kwargs) @requires_subprocess() - @force_not_colorized def assertSigInt(self, cmd, *args, **kwargs): # Use -E to ignore PYTHONSAFEPATH cmd = [sys.executable, '-E', *cmd] @@ -798,7 +796,6 @@ def assertSigInt(self, cmd, *args, **kwargs): def test_pymain_run_file(self): self.assertSigInt([self.ham]) - @force_not_colorized def test_pymain_run_file_runpy_run_module(self): tmp = self.ham.parent run_module = tmp / "run_module.py" @@ -812,7 +809,6 @@ def test_pymain_run_file_runpy_run_module(self): ) self.assertSigInt([run_module], cwd=tmp) - @force_not_colorized def test_pymain_run_file_runpy_run_module_as_main(self): tmp = self.ham.parent run_module_as_main = tmp / "run_module_as_main.py" @@ -826,22 +822,18 @@ def test_pymain_run_file_runpy_run_module_as_main(self): ) self.assertSigInt([run_module_as_main], cwd=tmp) - @force_not_colorized def test_pymain_run_command_run_module(self): self.assertSigInt( ["-c", "import runpy; runpy.run_module('ham')"], cwd=self.ham.parent, ) - @force_not_colorized def test_pymain_run_command(self): self.assertSigInt(["-c", "import ham"], cwd=self.ham.parent) - @force_not_colorized def test_pymain_run_stdin(self): self.assertSigInt([], input="import ham", cwd=self.ham.parent) - @force_not_colorized def test_pymain_run_module(self): ham = self.ham self.assertSigInt(["-m", ham.stem], cwd=ham.parent) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 1534ebeada674d..46b26210f3026c 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -396,7 +396,7 @@ def test_encoded_file(self): process = subprocess.Popen([sys.executable, "-c", "import sys; print(sys.stdout.encoding)"], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, env={}) + stderr=subprocess.STDOUT) stdout, stderr = process.communicate() output_encoding = str(stdout, 'ascii').splitlines()[0] diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py index 3d6e7ceb3c5d82..f685430a7d36ad 100644 --- a/Lib/test/test_tracemalloc.py +++ b/Lib/test/test_tracemalloc.py @@ -8,7 +8,6 @@ interpreter_requires_environment) from test import support from test.support import os_helper -from test.support import force_not_colorized try: import _testcapi @@ -980,7 +979,6 @@ def check_sys_xoptions_invalid(self, nframe): return self.fail(f"unexpected output: {stderr!a}") - @force_not_colorized def test_sys_xoptions_invalid(self): for nframe in INVALID_NFRAME: with self.subTest(nframe=nframe): diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py index 7df0e6a36e11fd..d3bf4ea7c7d437 100644 --- a/Lib/test/test_unicodedata.py +++ b/Lib/test/test_unicodedata.py @@ -12,7 +12,7 @@ import unicodedata import unittest from test.support import (open_urlresource, requires_resource, script_helper, - cpython_only, check_disallow_instantiation, force_not_colorized) + cpython_only, check_disallow_instantiation) class UnicodeMethodsTest(unittest.TestCase): @@ -277,7 +277,6 @@ def test_disallow_instantiation(self): # Ensure that the type disallows instantiation (bpo-43916) check_disallow_instantiation(self, unicodedata.UCD) - @force_not_colorized def test_failed_import_during_compiling(self): # Issue 4367 # Decoding \N escapes requires the unicodedata module. If it can't be diff --git a/Lib/traceback.py b/Lib/traceback.py index b5070912207998..5c0636419cdd1b 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -146,14 +146,15 @@ def _can_colorize(): return False if os.environ.get("PYTHON_COLORS") == "1": return True - if "NO_COLOR" in os.environ: - return False + 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": - return False + if not sys.flags.ignore_environment: + if "FORCE_COLOR" in os.environ: + return True + if os.environ.get("TERM") == "dumb": + return False try: return os.isatty(sys.stderr.fileno()) except io.UnsupportedOperation: From 73a4fb1de588c4ef824f385c441c2b67674260e1 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 10 Apr 2024 11:49:36 +0100 Subject: [PATCH 6/7] Implement extra checks in can_colorize --- Lib/traceback.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/traceback.py b/Lib/traceback.py index 5c0636419cdd1b..9d85dc401d8883 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -155,11 +155,16 @@ def _can_colorize(): 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() From dca336670f09695a498e921ae26a1558d942c4d2 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 17 Apr 2024 17:58:18 +0100 Subject: [PATCH 7/7] fixup! Merge remote-tracking branch 'upstream/main' into no_colorize Signed-off-by: Pablo Galindo --- Lib/test/test_traceback.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 46b26210f3026c..19611937fc278b 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4363,13 +4363,17 @@ def foo(): 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: + + 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'}):