jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/769693 )
Change subject: [cleanup] no longer use win32_unicode for Python 3.6+ ......................................................................
[cleanup] no longer use win32_unicode for Python 3.6+
This reverts revert 13f59c7f18593601a6635d2f94ad3066b7bec0a3.
- flash target in UI._print if necessary. This has to be done if the after a text is processed with UI._print and before a color is changed - get console_handle to set color attribute in Win32UI.encounter_color - define isatty() for Python 3.5 to keep the old behaviour - update and simplify tests - remove unused utils.FakeModule - ignore win32_unicode.py from coverage
Bug: T303373 Bug: T283808 Bug: T281042 Change-Id: Icd3f45a8a907bf2c4a11da68e25339b79de11eaf --- M .codecov.yml M pywikibot/userinterfaces/terminal_interface_base.py M pywikibot/userinterfaces/terminal_interface_win32.py M pywikibot/userinterfaces/win32_unicode.py M tests/ui_tests.py M tests/utils.py 6 files changed, 47 insertions(+), 73 deletions(-)
Approvals: Chris Maynor: Looks good to me, but someone else must approve Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/.codecov.yml b/.codecov.yml index 3314ceb..b19bd5e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -17,6 +17,7 @@ - pywikibot/backports.py - pywikibot/daemonize.py - pywikibot/families/__init__.py + - pywikibot/userinterfaces/win32_unicode.py - scripts/archive/ - scripts/userscripts/ - scripts/maintenance/colors.py diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py index 21c4c87..b855863 100755 --- a/pywikibot/userinterfaces/terminal_interface_base.py +++ b/pywikibot/userinterfaces/terminal_interface_base.py @@ -190,8 +190,13 @@
if current_color != next_color and colorized: # set the new color, but only if they change + # flush first to enable color change on Windows (T283808) + target_stream.flush() self.encounter_color(color_stack[-1], target_stream)
+ # finally flush stream to show the text, required for Windows (T303373) + target_stream.flush() + def output(self, text, targetStream=None) -> None: """Forward text to cache and flush if output is not locked.
diff --git a/pywikibot/userinterfaces/terminal_interface_win32.py b/pywikibot/userinterfaces/terminal_interface_win32.py index 1a0ef51..20d4b12 100755 --- a/pywikibot/userinterfaces/terminal_interface_win32.py +++ b/pywikibot/userinterfaces/terminal_interface_win32.py @@ -6,7 +6,8 @@ # import ctypes
-from pywikibot.userinterfaces import terminal_interface_base, win32_unicode +from pywikibot.tools import PYTHON_VERSION +from pywikibot.userinterfaces import terminal_interface_base
windowsColors = { @@ -37,16 +38,18 @@ def __init__(self) -> None: """Initializer.""" super().__init__() - (stdin, stdout, stderr, argv) = win32_unicode.get_unicode_console() - self.stdin = stdin - self.stdout = stdout - self.stderr = stderr - self.argv = argv - self.encoding = 'utf-8' + if PYTHON_VERSION == (3, 5): # issue1602 solved in Python 3.6 + from pywikibot.userinterfaces import win32_unicode + stdin, stdout, stderr, argv = win32_unicode.get_unicode_console() + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.argv = argv + self.encoding = 'utf-8'
def support_color(self, target_stream): """Return whether the target stream supports actually color.""" - return getattr(target_stream, '_hConsole', None) is not None + return target_stream.isatty()
def encounter_color(self, color, target_stream) -> None: """Set the new color.""" @@ -55,8 +58,21 @@ # Merge foreground/backgroung color if needed. if bg is not None: windows_color = windowsColors[bg] << 4 | windows_color - ctypes.windll.kernel32.SetConsoleTextAttribute( - target_stream._hConsole, windows_color) + + if target_stream == self.stdin: + addr = -10 + elif target_stream == self.stdout: + addr = -11 + elif target_stream == self.stderr: + addr = -12 + else: + super().encounter_color(color, target_stream) + + from ctypes.wintypes import DWORD, HANDLE + get_handle = ctypes.WINFUNCTYPE(HANDLE, DWORD)( + ('GetStdHandle', ctypes.windll.kernel32)) + handle = get_handle(DWORD(addr)) + ctypes.windll.kernel32.SetConsoleTextAttribute(handle, windows_color)
def _raw_input(self): data = self.stdin.readline() diff --git a/pywikibot/userinterfaces/win32_unicode.py b/pywikibot/userinterfaces/win32_unicode.py index ae7c1f5..75008a4 100755 --- a/pywikibot/userinterfaces/win32_unicode.py +++ b/pywikibot/userinterfaces/win32_unicode.py @@ -153,6 +153,10 @@ _complain('{}.writelines: {!r}'.format(self.name, e)) raise
+ def isatty(self): + """Return True if the stream is interactive.""" + return self._hConsole is not None +
def old_fileno(std_name): """Return the fileno or None if that doesn't work.""" diff --git a/tests/ui_tests.py b/tests/ui_tests.py index 8e78ea3..c9d8109 100644 --- a/tests/ui_tests.py +++ b/tests/ui_tests.py @@ -31,7 +31,6 @@ ) from pywikibot.userinterfaces.transliteration import NON_LATIN_DIGITS, _trans from tests.aspects import TestCase, TestCaseBase -from tests.utils import FakeModule
class Stream: @@ -487,7 +486,10 @@
"""Base class for test cases requiring that colorized output is active."""
+ net = False + expect_color = True + expected = 'Hello world you!'
def setUp(self): """Force colorized_output to True.""" @@ -500,16 +502,6 @@ pywikibot.config.colorized_output = self._old_config super().tearDown()
- -class FakeUnixTest(FakeUIColorizedTestBase, FakeUITest): - - """Test case to allow doing colorized Unix tests in any environment.""" - - net = False - - expected = 'Hello world you!' - ui_class = terminal_interface_unix.UnixUI - def _encounter_color(self, color, target_stream): """Verify that the written data, color and stream are correct.""" self.assertIs(target_stream, self.stream) @@ -520,6 +512,13 @@ sum(e[1] for e in self._colors[:self._index]))
+class FakeUnixTest(FakeUIColorizedTestBase, FakeUITest): + + """Test case to allow doing colorized Unix tests in any environment.""" + + ui_class = terminal_interface_unix.UnixUI + + class FakeWin32Test(FakeUIColorizedTestBase, FakeUITest):
""" @@ -530,54 +529,21 @@ import these will be unpatched. """
- net = False - - expected = 'Hello world you!' ui_class = terminal_interface_win32.Win32UI
def setUp(self): """Patch the ctypes import and initialize a stream and UI instance.""" super().setUp() - self._orig_ctypes = terminal_interface_win32.ctypes - ctypes = FakeModule.create_dotted('ctypes.windll.kernel32') - ctypes.windll.kernel32.SetConsoleTextAttribute = self._handle_setattr - terminal_interface_win32.ctypes = ctypes - self.stream._hConsole = object() - - def tearDown(self): - """Unpatch the ctypes import and check that all colors were used.""" - terminal_interface_win32.ctypes = self._orig_ctypes - super().tearDown() - - def _encounter_color(self, color, target_stream): - """Call the original method.""" - self._orig_encounter_color(color, target_stream) - - def _handle_setattr(self, handle, attribute): - """Dummy method to handle SetConsoleTextAttribute.""" - self.assertIs(handle, self.stream._hConsole) - color = self._colors[self._index][0] - self._index += 1 - color = terminal_interface_win32.windowsColors[color] - self.assertEqual(attribute, color) - self.assertLength(self.stream.getvalue(), - sum(e[1] for e in self._colors[:self._index])) + self.stream.isatty = lambda: self.expect_color
class FakeWin32UncolorizedTest(FakeWin32Test):
"""Test case to allow doing uncolorized Win32 tests in any environment."""
- net = False - expected = 'Hello world you! ***' expect_color = False
- def setUp(self): - """Change the local stream's console to None to disable colors.""" - super().setUp() - self.stream._hConsole = None -
if __name__ == '__main__': # pragma: no cover with suppress(SystemExit): diff --git a/tests/utils.py b/tests/utils.py index 5731aae..35f3372 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -11,7 +11,6 @@ import warnings from contextlib import contextmanager from subprocess import PIPE, Popen, TimeoutExpired -from types import ModuleType
import pywikibot from pywikibot import config @@ -60,23 +59,6 @@ return False
-class FakeModule(ModuleType): - - """An empty fake module.""" - - @classmethod - def create_dotted(cls, name): - """Create a chain of modules based on the name separated by periods.""" - modules = name.split('.') - mod = None - for mod_name in modules[::-1]: - module = cls(str(mod_name)) - if mod: - setattr(module, mod.__name__, mod) - mod = module - return mod - - class WarningSourceSkipContextManager(warnings.catch_warnings):
"""
pywikibot-commits@lists.wikimedia.org