https://github.com/python/cpython/commit/cc3dc8ab24f872c8d31ddbc4fce7ff1213ec5529
commit: cc3dc8ab24f872c8d31ddbc4fce7ff1213ec5529
branch: 3.13
author: Hugo van Kemenade <[email protected]>
committer: hugovk <[email protected]>
date: 2025-01-22T21:09:51+02:00
summary:

[3.13] gh-127873: Only check `sys.flags.ignore_environment` for `PYTHON*` env 
vars (GH-127877) (#129138)

files:
A Misc/NEWS.d/next/Library/2024-12-12-18-25-50.gh-issue-127873.WJRwfz.rst
M .github/CODEOWNERS
M Lib/_colorize.py
M Lib/test/support/__init__.py
M Lib/test/test__colorize.py
M Lib/test/test_capi/test_misc.py
M Lib/test/test_cmd_line_script.py
M Lib/test/test_compileall.py
M Lib/test/test_eof.py
M Lib/test/test_exceptions.py
M Lib/test/test_import/__init__.py
M Lib/test/test_inspect/test_inspect.py
M Lib/test/test_pyrepl/support.py
M Lib/test/test_pyrepl/test_pyrepl.py
M Lib/test/test_regrtest.py
M Lib/test/test_repl.py
M Lib/test/test_runpy.py
M Lib/test/test_tracemalloc.py
M Lib/test/test_unicodedata.py
M Lib/test/test_unittest/test_program.py
M Lib/test/test_unittest/test_result.py
M Lib/test/test_unittest/test_runner.py

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index f71214f11109f9..4ffbb428bc381d 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -267,3 +267,7 @@ Lib/test/test_interpreters/   @ericsnowcurrently
 # Config Parser
 Lib/configparser.py           @jaraco
 Lib/test/test_configparser.py @jaraco
+
+# Colorize
+Lib/_colorize.py              @hugovk
+Lib/test/test__colorize.py    @hugovk
diff --git a/Lib/_colorize.py b/Lib/_colorize.py
index daecf5b16c1965..deb2e854ce1ff8 100644
--- a/Lib/_colorize.py
+++ b/Lib/_colorize.py
@@ -40,15 +40,14 @@ def can_colorize(*, file=None) -> bool:
             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 not sys.flags.ignore_environment:
-        if "FORCE_COLOR" in os.environ:
-            return True
-        if os.environ.get("TERM") == "dumb":
-            return False
+    if "FORCE_COLOR" in os.environ:
+        return True
+    if os.environ.get("TERM") == "dumb":
+        return False
 
     if not hasattr(file, "fileno"):
         return False
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index e6a8ef1ddcc14d..d7fa6096d375c5 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -61,6 +61,7 @@
     "without_optimizer",
     "force_not_colorized",
     "force_not_colorized_test_class",
+    "make_clean_env",
     "BrokenIter",
     ]
 
@@ -2732,6 +2733,16 @@ def new_setUpClass(cls):
     return cls
 
 
+def make_clean_env() -> dict[str, str]:
+    clean_env = os.environ.copy()
+    for k in clean_env.copy():
+        if k.startswith("PYTHON"):
+            clean_env.pop(k)
+    clean_env.pop("FORCE_COLOR", None)
+    clean_env.pop("NO_COLOR", None)
+    return clean_env
+
+
 def initialized_with_pyrepl():
     """Detect whether PyREPL was used during Python initialization."""
     # If the main module has a __file__ attribute it's a Python module, which 
means PyREPL.
diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py
index 77e74fa3e23c2c..25519ba7e92e1f 100644
--- a/Lib/test/test__colorize.py
+++ b/Lib/test/test__colorize.py
@@ -3,7 +3,7 @@
 import unittest
 import unittest.mock
 import _colorize
-from test.support import force_not_colorized
+from test.support import force_not_colorized, make_clean_env
 
 ORIGINAL_CAN_COLORIZE = _colorize.can_colorize
 
@@ -17,6 +17,14 @@ def tearDownModule():
 
 
 class TestColorizeFunction(unittest.TestCase):
+    def setUp(self):
+        # Remove PYTHON* environment variables to isolate from local user
+        # settings and simulate running with `-E`. Such variables should be
+        # added to test methods later to patched os.environ.
+        patcher = unittest.mock.patch("os.environ", new=make_clean_env())
+        self.addCleanup(patcher.stop)
+        patcher.start()
+
     @force_not_colorized
     def test_colorized_detection_checks_for_environment_variables(self):
         flags = unittest.mock.MagicMock(ignore_environment=False)
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index ec37942bdb2825..f0b872627f6f93 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -75,6 +75,8 @@ class InstanceMethod:
     id = _testcapi.instancemethod(id)
     testfunction = _testcapi.instancemethod(testfunction)
 
+
[email protected]_not_colorized_test_class
 class CAPITest(unittest.TestCase):
 
     def test_instancemethod(self):
diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
index 3a5a8abf81e43d..1ec5e581f81d17 100644
--- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py
@@ -88,6 +88,8 @@ def _make_test_zip_pkg(zip_dir, zip_basename, pkg_name, 
script_basename,
     importlib.invalidate_caches()
     return to_return
 
+
[email protected]_not_colorized_test_class
 class CmdLineTest(unittest.TestCase):
     def _check_output(self, script_name, exit_code, data,
                              expected_file, expected_argv0,
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
index 812ff5e7f84461..21ecebc088d3df 100644
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -766,6 +766,7 @@ def test_d_compile_error(self):
         rc, out, err = self.assertRunNotOK('-q', '-d', 'dinsdale', self.pkgdir)
         self.assertRegex(out, b'File "dinsdale')
 
+    @support.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 e377383450e19d..582e5b6de6e687 100644
--- a/Lib/test/test_eof.py
+++ b/Lib/test/test_eof.py
@@ -2,7 +2,7 @@
 
 import sys
 from codecs import BOM_UTF8
-from test import support
+from test.support import force_not_colorized
 from test.support import os_helper
 from test.support import script_helper
 from test.support import warnings_helper
@@ -44,6 +44,7 @@ def test_EOFS(self):
         self.assertEqual(cm.exception.text, "ä = '''thîs is ")
         self.assertEqual(cm.exception.offset, 5)
 
+    @force_not_colorized
     def test_EOFS_with_file(self):
         expect = ("(<string>, line 1)")
         with os_helper.temp_dir() as temp_dir:
@@ -123,6 +124,7 @@ def test_line_continuation_EOF(self):
         self.assertEqual(str(cm.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 6a29bc38252fdb..c6fb848b8d7fda 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -1465,6 +1465,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
@@ -2157,6 +2158,7 @@ def test_multiline_not_highlighted(self):
                 self.assertEqual(result[-len(expected):], expected)
 
 
[email protected]_not_colorized_test_class
 class SyntaxErrorTests(unittest.TestCase):
     maxDiff = None
 
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index df712ec5a9d97d..d9246c0ea70a04 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -29,9 +29,20 @@
 
 from test.support import os_helper
 from test.support import (
-    STDLIB_DIR, swap_attr, swap_item, cpython_only, is_apple_mobile, 
is_emscripten,
-    is_wasi, run_in_subinterp, run_in_subinterp_with_config, Py_TRACE_REFS,
-    requires_gil_enabled, Py_GIL_DISABLED)
+    STDLIB_DIR,
+    swap_attr,
+    swap_item,
+    cpython_only,
+    is_apple_mobile,
+    is_emscripten,
+    is_wasi,
+    run_in_subinterp,
+    run_in_subinterp_with_config,
+    Py_TRACE_REFS,
+    requires_gil_enabled,
+    Py_GIL_DISABLED,
+    force_not_colorized_test_class,
+)
 from test.support.import_helper import (
     forget, make_legacy_pyc, unlink, unload, ready_to_import,
     DirsOnSysPath, CleanImport, import_module)
@@ -352,6 +363,7 @@ def _from_subinterp(cls, name, interpid, pipe, 
script_kwargs):
         return cls.parse(text.decode())
 
 
+@force_not_colorized_test_class
 class ImportTests(unittest.TestCase):
 
     def setUp(self):
diff --git a/Lib/test/test_inspect/test_inspect.py 
b/Lib/test/test_inspect/test_inspect.py
index 34ae951b38ad58..f30dc7affda11a 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -880,6 +880,7 @@ def test_getsource_stdlib_decimal(self):
         self.assertEqual(src.splitlines(True), lines)
 
 class TestGetsourceInteractive(unittest.TestCase):
+    @support.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_pyrepl/support.py b/Lib/test/test_pyrepl/support.py
index 672d4896c92283..45e3bf758f17de 100644
--- a/Lib/test/test_pyrepl/support.py
+++ b/Lib/test/test_pyrepl/support.py
@@ -101,16 +101,6 @@ def handle_all_events(
 )
 
 
-def make_clean_env() -> dict[str, str]:
-    clean_env = os.environ.copy()
-    for k in clean_env.copy():
-        if k.startswith("PYTHON"):
-            clean_env.pop(k)
-    clean_env.pop("FORCE_COLOR", None)
-    clean_env.pop("NO_COLOR", None)
-    return clean_env
-
-
 class FakeConsole(Console):
     def __init__(self, events, encoding="utf-8") -> None:
         self.events = iter(events)
diff --git a/Lib/test/test_pyrepl/test_pyrepl.py 
b/Lib/test/test_pyrepl/test_pyrepl.py
index a20e79b674a916..b90b5a05adfa4a 100644
--- a/Lib/test/test_pyrepl/test_pyrepl.py
+++ b/Lib/test/test_pyrepl/test_pyrepl.py
@@ -10,7 +10,7 @@
 import tempfile
 from unittest import TestCase, skipUnless, skipIf
 from unittest.mock import patch
-from test.support import force_not_colorized
+from test.support import force_not_colorized, make_clean_env
 from test.support import SHORT_TIMEOUT
 from test.support.import_helper import import_module
 from test.support.os_helper import unlink
@@ -23,7 +23,6 @@
     multiline_input,
     code_to_events,
     clean_screen,
-    make_clean_env,
 )
 from _pyrepl.console import Event
 from _pyrepl.readline import (ReadlineAlikeReader, ReadlineConfig,
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index 54b6a16a0dab05..a5c9617bb07fd6 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -789,6 +789,7 @@ def test_finds_expected_number_of_tests(self):
                            f'{", ".join(output.splitlines())}')
 
 
[email protected]_not_colorized_test_class
 class ProgramsTestCase(BaseTestCase):
     """
     Test various ways to run the Python test suite. Use options close
@@ -902,6 +903,7 @@ def test_pcbuild_rt(self):
         self.run_batch(script, *rt_args, *self.regrtest_args, *self.tests)
 
 
[email protected]_not_colorized_test_class
 class ArgsTestCase(BaseTestCase):
     """
     Test arguments of the Python test suite.
diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py
index e764e60560db23..356ff5b198d637 100644
--- a/Lib/test/test_repl.py
+++ b/Lib/test/test_repl.py
@@ -70,6 +70,7 @@ def run_on_interactive_mode(source):
     return output
 
 
[email protected]_not_colorized_test_class
 class TestInteractiveInterpreter(unittest.TestCase):
 
     @cpython_only
@@ -273,6 +274,8 @@ def test_asyncio_repl_is_ok(self):
 
         self.assertEqual(exit_code, 0, "".join(output))
 
+
[email protected]_not_colorized_test_class
 class TestInteractiveModeSyntaxErrors(unittest.TestCase):
 
     def test_interactive_syntax_error_correct_line(self):
diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py
index b64383f6546f31..ada78ec8e6b0c7 100644
--- a/Lib/test/test_runpy.py
+++ b/Lib/test/test_runpy.py
@@ -12,8 +12,14 @@
 import textwrap
 import unittest
 import warnings
-from test.support import (infinite_recursion, no_tracing, verbose,
-                          requires_subprocess, requires_resource)
+from test.support import (
+    force_not_colorized_test_class,
+    infinite_recursion,
+    no_tracing,
+    requires_resource,
+    requires_subprocess,
+    verbose,
+)
 from test.support.import_helper import forget, make_legacy_pyc, unload
 from test.support.os_helper import create_empty_file, temp_dir, FakePath
 from test.support.script_helper import make_script, make_zip_script
@@ -758,6 +764,7 @@ def test_encoding(self):
             self.assertEqual(result['s'], "non-ASCII: h\xe9")
 
 
+@force_not_colorized_test_class
 class TestExit(unittest.TestCase):
     STATUS_CONTROL_C_EXIT = 0xC000013A
     EXPECTED_CODE = (
diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py
index a848363fcd1de9..238ae14b388c76 100644
--- a/Lib/test/test_tracemalloc.py
+++ b/Lib/test/test_tracemalloc.py
@@ -981,6 +981,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..2cf367a2cfe85b 100644
--- a/Lib/test/test_unicodedata.py
+++ b/Lib/test/test_unicodedata.py
@@ -11,8 +11,14 @@
 import sys
 import unicodedata
 import unittest
-from test.support import (open_urlresource, requires_resource, script_helper,
-                          cpython_only, check_disallow_instantiation)
+from test.support import (
+    open_urlresource,
+    requires_resource,
+    script_helper,
+    cpython_only,
+    check_disallow_instantiation,
+    force_not_colorized,
+)
 
 
 class UnicodeMethodsTest(unittest.TestCase):
@@ -277,6 +283,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_unittest/test_program.py 
b/Lib/test/test_unittest/test_program.py
index 7241cf59f73d4f..aa7e8b712fd763 100644
--- a/Lib/test/test_unittest/test_program.py
+++ b/Lib/test/test_unittest/test_program.py
@@ -7,6 +7,7 @@
 from test.test_unittest.test_result import BufferedWriter
 
 
[email protected]_not_colorized_test_class
 class Test_TestProgram(unittest.TestCase):
 
     def test_discovery_from_dotted_path(self):
diff --git a/Lib/test/test_unittest/test_result.py 
b/Lib/test/test_unittest/test_result.py
index 144f20176f8b18..4d552d54e9a6df 100644
--- a/Lib/test/test_unittest/test_result.py
+++ b/Lib/test/test_unittest/test_result.py
@@ -33,6 +33,7 @@ def bad_cleanup2():
     raise ValueError('bad cleanup2')
 
 
+@force_not_colorized_test_class
 class Test_TestResult(unittest.TestCase):
     # Note: there are not separate tests for TestResult.wasSuccessful(),
     # TestResult.errors, TestResult.failures, TestResult.testsRun or
@@ -456,6 +457,7 @@ def test(result):
         self.assertTrue(stream.getvalue().endswith('\n\nOK\n'))
 
 
+@force_not_colorized_test_class
 class Test_TextTestResult(unittest.TestCase):
     maxDiff = None
 
diff --git a/Lib/test/test_unittest/test_runner.py 
b/Lib/test/test_unittest/test_runner.py
index 1b9cef43e3f9c5..4d3cfd60b8d9c3 100644
--- a/Lib/test/test_unittest/test_runner.py
+++ b/Lib/test/test_unittest/test_runner.py
@@ -106,6 +106,7 @@ def cleanup2(*args, **kwargs):
         self.assertTrue(test.doCleanups())
         self.assertEqual(cleanups, [(2, (), {}), (1, (1, 2, 3), 
dict(four='hello', five='goodbye'))])
 
+    @support.force_not_colorized
     def testCleanUpWithErrors(self):
         class TestableTest(unittest.TestCase):
             def testNothing(self):
@@ -249,6 +250,7 @@ def testNothing(self):
         self.assertEqual(test._cleanups, [])
 
 
[email protected]_not_colorized_test_class
 class TestClassCleanup(unittest.TestCase):
     def test_addClassCleanUp(self):
         class TestableTest(unittest.TestCase):
@@ -601,6 +603,7 @@ class EmptyTest(unittest.TestCase):
         self.assertIn("\nNO TESTS RAN\n", runner.stream.getvalue())
 
 
[email protected]_not_colorized_test_class
 class TestModuleCleanUp(unittest.TestCase):
     def test_add_and_do_ModuleCleanup(self):
         module_cleanups = []
@@ -1318,6 +1321,7 @@ def MockResultClass(*args):
         expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY)
         self.assertEqual(runner._makeResult(), expectedresult)
 
+    @support.force_not_colorized
     @support.requires_subprocess()
     def test_warnings(self):
         """
diff --git 
a/Misc/NEWS.d/next/Library/2024-12-12-18-25-50.gh-issue-127873.WJRwfz.rst 
b/Misc/NEWS.d/next/Library/2024-12-12-18-25-50.gh-issue-127873.WJRwfz.rst
new file mode 100644
index 00000000000000..d7575c7efb6e88
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-12-12-18-25-50.gh-issue-127873.WJRwfz.rst
@@ -0,0 +1,3 @@
+When ``-E`` is set, only ignore ``PYTHON_COLORS`` and not
+``FORCE_COLOR``/``NO_COLOR``/``TERM`` when colourising output.
+Patch by Hugo van Kemenade.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to