From fe72f380f17e0c9c5fa023bbf629c34ac2cc12ba Mon Sep 17 00:00:00 2001 From: Max Tilley <1138504+3tilley@users.noreply.github.com> Date: Tue, 26 Oct 2021 01:02:50 +0100 Subject: [PATCH 1/2] With tests --- colorama/ansitowin32.py | 26 +++++++++++------ colorama/tests/isatty_test.py | 54 ++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/colorama/ansitowin32.py b/colorama/ansitowin32.py index 4f3497ed..e625a03e 100644 --- a/colorama/ansitowin32.py +++ b/colorama/ansitowin32.py @@ -1,4 +1,5 @@ # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from io import UnsupportedOperation import re import sys import os @@ -19,6 +20,11 @@ winterm = WinTerm() +class FileNameInfo(ctypes.Structure): + _fields_ = [('FileNameLength', ctypes.c_ulong), + ('FileName', ctypes.c_wchar * 40)] + + def is_msys_cygwin_tty(stream): if not hasattr(stream, "fileno"): return False @@ -29,17 +35,18 @@ def is_msys_cygwin_tty(stream): if msvcrt is None: return False - fileno = stream.fileno() - handle = msvcrt.get_osfhandle(fileno) - FileNameInfo = 2 + try: + fileno = stream.fileno() + except UnsupportedOperation: + # StringIO for example has the fileno attribute but doesn't support calling it + return False - class FILE_NAME_INFO(ctypes.Structure): - _fields_ = [('FileNameLength', ctypes.c_ulong), - ('FileName', ctypes.c_wchar * 40)] + handle = msvcrt.get_osfhandle(fileno) + FILE_NAME_INFO = 2 - info = FILE_NAME_INFO() + info = FileNameInfo() ret = ctypes.windll.kernel32.GetFileInformationByHandleEx(handle, - FileNameInfo, + FILE_NAME_INFO, ctypes.byref(info), ctypes.sizeof(info)) if ret == 0: @@ -47,6 +54,9 @@ class FILE_NAME_INFO(ctypes.Structure): msys_pattern = r"\\msys-[0-9a-f]{16}-pty\d-(to|from)-master" cygwin_pattern = r"\\cygwin-[0-9a-f]{16}-pty\d-(to|from)-master" + print() + print(info.FileName) + print() return re.match(msys_pattern, info.FileName) is not None or \ re.match(cygwin_pattern, info.FileName) is not None diff --git a/colorama/tests/isatty_test.py b/colorama/tests/isatty_test.py index 0f84e4be..86cc006c 100644 --- a/colorama/tests/isatty_test.py +++ b/colorama/tests/isatty_test.py @@ -1,8 +1,11 @@ # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. import sys +from io import StringIO from unittest import TestCase, main -from ..ansitowin32 import StreamWrapper, AnsiToWin32 +from mock import patch + +from ..ansitowin32 import StreamWrapper, AnsiToWin32, is_msys_cygwin_tty from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY @@ -52,6 +55,55 @@ def test_withPycharmStreamWrapped(self): self.assertTrue(AnsiToWin32(sys.stdout).stream.isatty()) self.assertTrue(AnsiToWin32(sys.stderr).stream.isatty()) + @patch("colorama.ansitowin32.is_msys_cygwin_tty", return_value=False) + def test_isattyCorrectForMintty(self, mock_fn): + self.assertFalse(is_a_tty(StreamTTY())) + self.assertFalse(is_a_tty(StreamNonTTY())) + mock_fn.assert_called_once() + + @patch("colorama.ansitowin32.is_msys_cygwin_tty", return_value=True) + def test_isattyCorrectForMintty(self, mock_fn): + self.assertTrue(is_a_tty(StreamNonTTY())) + self.assertTrue(is_a_tty(StreamTTY())) + mock_fn.assert_called() + +class MinttyTest(TestCase): + """Tests for the detection of mintty / msys/ cygwin + + They're arguably a little brittle to the exact detection implementation, so can be refactored + if the implementation changes. + """ + + @patch("colorama.ansitowin32.msvcrt", None) + @patch("io.StringIO") + def test_falseNotOnWindows(self, mock_stream): + mock_stream.fileno.return_value = 10 + self.assertFalse(is_msys_cygwin_tty(mock_stream)) + + def test_falseForIoString(self): + self.assertFalse(is_msys_cygwin_tty(StringIO)) + + @patch("colorama.ansitowin32.ctypes.windll.kernel32", None) + @patch("io.StringIO") + def test_falseForIoString(self, mock_stream): + self.assertFalse(is_msys_cygwin_tty(StreamTTY())) + + @patch("colorama.ansitowin32.ctypes.windll.kernel32.GetFileInformationByHandleEx", + return_value=0) + @patch("io.StringIO") + def test_falseForIoString(self, mock_win32_call, mock_stream): + mock_stream.fileno.return_value = 10 + self.assertFalse(is_msys_cygwin_tty(StreamTTY())) + + @patch("ctypes.windll.kernel32.GetFileInformationByHandleEx", + return_value=1) + @patch("io.StringIO") + def test_falseForIoString(self, mock_file_name, mock_stream): + mock_stream.fileno.return_value = 10 + + with patch("colorama.ansitowin32.FileNameInfo.FileName") as mock_filename_info: + mock_filename_info.return_value = r"\\msys-0000000000000000-pty3-to-master" + self.assertTrue(is_msys_cygwin_tty(mock_stream())) if __name__ == '__main__': main() From a538f6367a06de393ffb28fae7d6df47e38603d5 Mon Sep 17 00:00:00 2001 From: Max Tilley <1138504+3tilley@users.noreply.github.com> Date: Tue, 26 Oct 2021 02:04:18 +0100 Subject: [PATCH 2/2] Completed tests --- colorama/ansitowin32.py | 5 +-- colorama/tests/isatty_test.py | 76 +++++++++++++++++++++-------------- colorama/tests/utils.py | 7 ++++ 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/colorama/ansitowin32.py b/colorama/ansitowin32.py index e625a03e..46e54750 100644 --- a/colorama/ansitowin32.py +++ b/colorama/ansitowin32.py @@ -21,6 +21,7 @@ class FileNameInfo(ctypes.Structure): + """Struct to get FileNameInfo from the win32api""" _fields_ = [('FileNameLength', ctypes.c_ulong), ('FileName', ctypes.c_wchar * 40)] @@ -54,9 +55,7 @@ def is_msys_cygwin_tty(stream): msys_pattern = r"\\msys-[0-9a-f]{16}-pty\d-(to|from)-master" cygwin_pattern = r"\\cygwin-[0-9a-f]{16}-pty\d-(to|from)-master" - print() - print(info.FileName) - print() + return re.match(msys_pattern, info.FileName) is not None or \ re.match(cygwin_pattern, info.FileName) is not None diff --git a/colorama/tests/isatty_test.py b/colorama/tests/isatty_test.py index 86cc006c..ec2e7d9a 100644 --- a/colorama/tests/isatty_test.py +++ b/colorama/tests/isatty_test.py @@ -1,12 +1,12 @@ # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. import sys from io import StringIO -from unittest import TestCase, main +from unittest import TestCase, main, skipUnless -from mock import patch +from mock import patch, PropertyMock -from ..ansitowin32 import StreamWrapper, AnsiToWin32, is_msys_cygwin_tty -from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY +from ..ansitowin32 import StreamWrapper, AnsiToWin32, is_msys_cygwin_tty, FileNameInfo +from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY, StreamNonTTYWithFileNo def is_a_tty(stream): @@ -75,35 +75,49 @@ class MinttyTest(TestCase): """ @patch("colorama.ansitowin32.msvcrt", None) - @patch("io.StringIO") - def test_falseNotOnWindows(self, mock_stream): - mock_stream.fileno.return_value = 10 - self.assertFalse(is_msys_cygwin_tty(mock_stream)) + def test_falseNotOnWindows(self): + self.assertFalse(is_msys_cygwin_tty(StreamNonTTYWithFileNo())) def test_falseForIoString(self): - self.assertFalse(is_msys_cygwin_tty(StringIO)) - - @patch("colorama.ansitowin32.ctypes.windll.kernel32", None) - @patch("io.StringIO") - def test_falseForIoString(self, mock_stream): - self.assertFalse(is_msys_cygwin_tty(StreamTTY())) - - @patch("colorama.ansitowin32.ctypes.windll.kernel32.GetFileInformationByHandleEx", - return_value=0) - @patch("io.StringIO") - def test_falseForIoString(self, mock_win32_call, mock_stream): - mock_stream.fileno.return_value = 10 - self.assertFalse(is_msys_cygwin_tty(StreamTTY())) - - @patch("ctypes.windll.kernel32.GetFileInformationByHandleEx", - return_value=1) - @patch("io.StringIO") - def test_falseForIoString(self, mock_file_name, mock_stream): - mock_stream.fileno.return_value = 10 - - with patch("colorama.ansitowin32.FileNameInfo.FileName") as mock_filename_info: - mock_filename_info.return_value = r"\\msys-0000000000000000-pty3-to-master" - self.assertTrue(is_msys_cygwin_tty(mock_stream())) + self.assertFalse(is_msys_cygwin_tty(StringIO())) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + @patch("ctypes.windll.kernel32", None) + def test_falseIfKernelModuleUnavailable(self): + self.assertFalse(is_msys_cygwin_tty(StreamNonTTYWithFileNo())) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + @patch("ctypes.windll.kernel32.GetFileInformationByHandleEx", return_value=0) + @patch("msvcrt.get_osfhandle", return_value=10) + def test_falseIfWin32CallFails(self, mock_win32_call, mock_handle_call): + self.assertFalse(is_msys_cygwin_tty(StreamNonTTYWithFileNo())) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + @patch("ctypes.windll.kernel32.GetFileInformationByHandleEx", return_value=1) + @patch("msvcrt.get_osfhandle", return_value=1000) + def test_trueForMsys(self, mock_file_call, mock_handle_call): + + with patch.object(FileNameInfo, "FileName", new_callable=PropertyMock) as mock_filename_info: + mock_filename_info.return_value = r"\msys-0000000000000000-pty3-to-master" + self.assertTrue(is_msys_cygwin_tty(StreamNonTTYWithFileNo())) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + @patch("ctypes.windll.kernel32.GetFileInformationByHandleEx", return_value=1) + @patch("msvcrt.get_osfhandle", return_value=1000) + def test_trueForCygwin(self, mock_file_call, mock_handle_call): + + with patch.object(FileNameInfo, "FileName", new_callable=PropertyMock) as mock_filename_info: + mock_filename_info.return_value = r"\cygwin-0000000000000000-pty3-to-master" + self.assertTrue(is_msys_cygwin_tty(StreamNonTTYWithFileNo())) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + @patch("ctypes.windll.kernel32.GetFileInformationByHandleEx", return_value=1) + @patch("msvcrt.get_osfhandle", return_value=1000) + def test_falseForAnythingElse(self, mock_file_call, mock_handle_call): + + with patch.object(FileNameInfo, "FileName", new_callable=PropertyMock) as mock_filename_info: + mock_filename_info.return_value = r"\random-0000000000000000-pty3-to-master" + self.assertFalse(is_msys_cygwin_tty(StreamNonTTYWithFileNo())) if __name__ == '__main__': main() diff --git a/colorama/tests/utils.py b/colorama/tests/utils.py index de2abf55..2f3d1545 100644 --- a/colorama/tests/utils.py +++ b/colorama/tests/utils.py @@ -14,6 +14,13 @@ class StreamNonTTY(StringIO): def isatty(self): return False +class StreamNonTTYWithFileNo(StringIO): + def isatty(self): + return False + + def fileno(self): + return 10 + @contextmanager def osname(name): orig = os.name