mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-141570: can_colorize: Expect fileno() to raise OSError, as documented (#141716) In Fedora, we've been given a slightly incomplete reproducer for a problematic Python 3.14 color-related change in argparse that leads to an exception when Python is used from mod_wsgi: https://bugzilla.redhat.com/2414940 mod_wsgi replaces sys.stdout with a custom object that raises OSError on .fileno():8460dbfcd5/src/server/wsgi_logger.c (L434-L440)This should be supported, as the documentation of fileno explicitly says: > An OSError is raised if the IO object does not use a file descriptor. https://docs.python.org/3.14/library/io.html#io.IOBase.fileno The previously expected exception inherits from OSError, so it is still expected. Fixes https://github.com/python/cpython/issues/141570 (cherry picked from commit96f496a949) Co-authored-by: Miro HronĨok <miro@hroncok.cz> Co-authored-by: Cody Maloney <cmaloney@users.noreply.github.com>
145 lines
6.5 KiB
Python
145 lines
6.5 KiB
Python
import contextlib
|
|
import io
|
|
import sys
|
|
import unittest
|
|
import unittest.mock
|
|
import _colorize
|
|
from test.support.os_helper import EnvironmentVarGuard
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def clear_env():
|
|
with EnvironmentVarGuard() as mock_env:
|
|
mock_env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS", "TERM")
|
|
yield mock_env
|
|
|
|
|
|
def supports_virtual_terminal():
|
|
if sys.platform == "win32":
|
|
return unittest.mock.patch("nt._supports_virtual_terminal", return_value=True)
|
|
else:
|
|
return contextlib.nullcontext()
|
|
|
|
|
|
class TestColorizeFunction(unittest.TestCase):
|
|
def test_colorized_detection_checks_for_environment_variables(self):
|
|
def check(env, fallback, expected):
|
|
with (self.subTest(env=env, fallback=fallback),
|
|
clear_env() as mock_env):
|
|
mock_env.update(env)
|
|
isatty_mock.return_value = fallback
|
|
stdout_mock.isatty.return_value = fallback
|
|
self.assertEqual(_colorize.can_colorize(), expected)
|
|
|
|
with (unittest.mock.patch("os.isatty") as isatty_mock,
|
|
unittest.mock.patch("sys.stdout") as stdout_mock,
|
|
supports_virtual_terminal()):
|
|
stdout_mock.fileno.return_value = 1
|
|
|
|
for fallback in False, True:
|
|
check({}, fallback, fallback)
|
|
check({'TERM': 'dumb'}, fallback, False)
|
|
check({'TERM': 'xterm'}, fallback, fallback)
|
|
check({'TERM': ''}, fallback, fallback)
|
|
check({'FORCE_COLOR': '1'}, fallback, True)
|
|
check({'FORCE_COLOR': '0'}, fallback, True)
|
|
check({'FORCE_COLOR': ''}, fallback, fallback)
|
|
check({'NO_COLOR': '1'}, fallback, False)
|
|
check({'NO_COLOR': '0'}, fallback, False)
|
|
check({'NO_COLOR': ''}, fallback, fallback)
|
|
|
|
check({'TERM': 'dumb', 'FORCE_COLOR': '1'}, False, True)
|
|
check({'FORCE_COLOR': '1', 'NO_COLOR': '1'}, True, False)
|
|
|
|
for ignore_environment in False, True:
|
|
# Simulate running with or without `-E`.
|
|
flags = unittest.mock.MagicMock(ignore_environment=ignore_environment)
|
|
with unittest.mock.patch("sys.flags", flags):
|
|
check({'PYTHON_COLORS': '1'}, True, True)
|
|
check({'PYTHON_COLORS': '1'}, False, not ignore_environment)
|
|
check({'PYTHON_COLORS': '0'}, True, ignore_environment)
|
|
check({'PYTHON_COLORS': '0'}, False, False)
|
|
for fallback in False, True:
|
|
check({'PYTHON_COLORS': 'x'}, fallback, fallback)
|
|
check({'PYTHON_COLORS': ''}, fallback, fallback)
|
|
|
|
check({'TERM': 'dumb', 'PYTHON_COLORS': '1'}, False, not ignore_environment)
|
|
check({'NO_COLOR': '1', 'PYTHON_COLORS': '1'}, False, not ignore_environment)
|
|
check({'FORCE_COLOR': '1', 'PYTHON_COLORS': '0'}, True, ignore_environment)
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "requires Windows")
|
|
def test_colorized_detection_checks_on_windows(self):
|
|
with (clear_env(),
|
|
unittest.mock.patch("os.isatty") as isatty_mock,
|
|
unittest.mock.patch("sys.stdout") as stdout_mock,
|
|
supports_virtual_terminal() as vt_mock):
|
|
stdout_mock.fileno.return_value = 1
|
|
isatty_mock.return_value = True
|
|
stdout_mock.isatty.return_value = True
|
|
|
|
vt_mock.return_value = True
|
|
self.assertEqual(_colorize.can_colorize(), True)
|
|
vt_mock.return_value = False
|
|
self.assertEqual(_colorize.can_colorize(), False)
|
|
import nt
|
|
del nt._supports_virtual_terminal
|
|
self.assertEqual(_colorize.can_colorize(), False)
|
|
|
|
def test_colorized_detection_checks_for_std_streams(self):
|
|
with (clear_env(),
|
|
unittest.mock.patch("os.isatty") as isatty_mock,
|
|
unittest.mock.patch("sys.stdout") as stdout_mock,
|
|
unittest.mock.patch("sys.stderr") as stderr_mock,
|
|
supports_virtual_terminal()):
|
|
stdout_mock.fileno.return_value = 1
|
|
stderr_mock.fileno.side_effect = ZeroDivisionError
|
|
stderr_mock.isatty.side_effect = ZeroDivisionError
|
|
|
|
isatty_mock.return_value = True
|
|
stdout_mock.isatty.return_value = True
|
|
self.assertEqual(_colorize.can_colorize(), True)
|
|
|
|
isatty_mock.return_value = False
|
|
stdout_mock.isatty.return_value = False
|
|
self.assertEqual(_colorize.can_colorize(), False)
|
|
|
|
def test_colorized_detection_checks_for_file(self):
|
|
with clear_env(), supports_virtual_terminal():
|
|
|
|
with unittest.mock.patch("os.isatty") as isatty_mock:
|
|
file = unittest.mock.MagicMock()
|
|
file.fileno.return_value = 1
|
|
isatty_mock.return_value = True
|
|
self.assertEqual(_colorize.can_colorize(file=file), True)
|
|
isatty_mock.return_value = False
|
|
self.assertEqual(_colorize.can_colorize(file=file), False)
|
|
|
|
# No file.fileno.
|
|
with unittest.mock.patch("os.isatty", side_effect=ZeroDivisionError):
|
|
file = unittest.mock.MagicMock(spec=['isatty'])
|
|
file.isatty.return_value = True
|
|
self.assertEqual(_colorize.can_colorize(file=file), False)
|
|
|
|
# file.fileno() raises io.UnsupportedOperation.
|
|
with unittest.mock.patch("os.isatty", side_effect=ZeroDivisionError):
|
|
file = unittest.mock.MagicMock()
|
|
file.fileno.side_effect = io.UnsupportedOperation
|
|
file.isatty.return_value = True
|
|
self.assertEqual(_colorize.can_colorize(file=file), True)
|
|
file.isatty.return_value = False
|
|
self.assertEqual(_colorize.can_colorize(file=file), False)
|
|
|
|
# The documentation for file.fileno says:
|
|
# > An OSError is raised if the IO object does not use a file descriptor.
|
|
# gh-141570: Check OSError is caught and handled
|
|
with unittest.mock.patch("os.isatty", side_effect=ZeroDivisionError):
|
|
file = unittest.mock.MagicMock()
|
|
file.fileno.side_effect = OSError
|
|
file.isatty.return_value = True
|
|
self.assertEqual(_colorize.can_colorize(file=file), True)
|
|
file.isatty.return_value = False
|
|
self.assertEqual(_colorize.can_colorize(file=file), False)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|