From 40d77dc3005e8d1645499fe8d6c773cee6c45921 Mon Sep 17 00:00:00 2001 From: Abhinav Maurya Date: Thu, 29 Oct 2026 10:10:03 +0530 Subject: [PATCH] Fix Windows ANSI initialization using STD_OUTPUT_HANDLE constant (#721) --- fire/formatting_test.py | 42 ++++++++++++++++++++++++++++++++++++++ fire/formatting_windows.py | 5 +++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/fire/formatting_test.py b/fire/formatting_test.py index e0f6699d2..0f6e42ac3 100644 --- a/fire/formatting_test.py +++ b/fire/formatting_test.py @@ -14,7 +14,10 @@ """Tests for formatting.py.""" +from unittest import mock + from fire import formatting +from fire import formatting_windows from fire import testutils LINE_LENGTH = 80 @@ -74,5 +77,44 @@ def test_ellipsis_middle_truncate_not_enough_space(self): self.assertEqual('1000000000L', truncated_text) +class FormattingWindowsTest(testutils.BaseTestCase): + + def test_std_output_handle_constant(self): + self.assertEqual(formatting_windows.STD_OUTPUT_HANDLE, -11) + + @mock.patch.object(formatting_windows, 'HAS_COLORAMA', True) + @mock.patch.object(formatting_windows, 'colorama', create=True) + @mock.patch('platform.release', return_value='10') + @mock.patch('sys.stdout.isatty', return_value=True) + def test_initialize_or_disable_windows_10( + self, mock_isatty, mock_release, mock_colorama + ): + fake_kernel32 = mock.MagicMock() + fake_kernel32.GetStdHandle.return_value = 42 + fake_kernel32.GetConsoleMode.return_value = 1 + fake_kernel32.SetConsoleMode.return_value = 1 + + with mock.patch.object( + formatting_windows.ctypes, 'windll', create=True + ) as mock_windll: + mock_windll.kernel32 = fake_kernel32 + with mock.patch.object( + formatting_windows.ctypes, 'wintypes', create=True + ) as mock_wintypes: + with mock.patch.object( + formatting_windows.ctypes, 'byref', return_value=mock.MagicMock() + ): + mock_dword = mock.MagicMock() + mock_dword.value = 0 + mock_wintypes.DWORD.return_value = mock_dword + + formatting_windows.initialize_or_disable() + + fake_kernel32.GetStdHandle.assert_called_once_with( + formatting_windows.STD_OUTPUT_HANDLE + ) + mock_colorama.init.assert_called_once_with(wrap=False) + + if __name__ == '__main__': testutils.main() diff --git a/fire/formatting_windows.py b/fire/formatting_windows.py index 749ab6d09..f27bb882a 100644 --- a/fire/formatting_windows.py +++ b/fire/formatting_windows.py @@ -17,7 +17,6 @@ import ctypes import os import platform -import subprocess import sys try: @@ -26,6 +25,8 @@ except ImportError: HAS_COLORAMA = False +STD_OUTPUT_HANDLE = -11 + def initialize_or_disable(): """Enables ANSI processing on Windows or disables it as needed.""" @@ -40,7 +41,7 @@ def initialize_or_disable(): wrap = False kernel32 = ctypes.windll.kernel32 enable_virtual_terminal_processing = 0x04 - out_handle = kernel32.GetStdHandle(subprocess.STD_OUTPUT_HANDLE) # pylint: disable=line-too-long, + out_handle = kernel32.GetStdHandle(STD_OUTPUT_HANDLE) # GetConsoleMode fails if the terminal isn't native. mode = ctypes.wintypes.DWORD() if kernel32.GetConsoleMode(out_handle, ctypes.byref(mode)) == 0: