1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
from __future__ import annotations
import sys
# noinspection PyProtectedMember
from ctypes import Structure, _SimpleCData # noqa: PLC2701
from ctypes.wintypes import DWORD, WCHAR, WORD
from types import ModuleType
from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar
from unittest.mock import ANY, Mock, call
import pytest
from streamlink_cli.console.windows import COORD, WindowsConsole
if TYPE_CHECKING:
from io import TextIOWrapper
_TCTypesType = TypeVar("_TCTypesType")
class _CTypesComparable(Generic[_TCTypesType]):
"""Allow comparing ctypes data types with built-in types for equality"""
_type: ClassVar[type]
def __init__(self, data: _TCTypesType):
self.data: tuple = self._get_data(data)
def _get_data(self, data: _TCTypesType) -> tuple: # pragma: no cover
raise NotImplementedError
def __eq__(self, other):
if isinstance(other, self._type):
return self.__eq__(type(self)(other))
if not isinstance(other, type(self)): # pragma: no cover
return False
return self.data == other.data
def __hash__(self): # pragma: no cover
return super().__hash__()
class EqSimpleCData(_CTypesComparable[_SimpleCData]):
_type: ClassVar = _SimpleCData
def _get_data(self, data: _SimpleCData) -> tuple:
# noinspection PyProtectedMember
return data._type_, data.value # type: ignore[attr-defined]
class EqStructure(_CTypesComparable[Structure]):
_type: ClassVar = Structure
def _get_data(self, data: Structure) -> tuple:
# noinspection PyProtectedMember
return tuple(getattr(data, item) for (item, *_) in data._fields_)
@pytest.fixture(autouse=True)
def _mock_byref(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr("streamlink_cli.console.windows.byref", Mock(side_effect=lambda obj, *_, **__: obj))
@pytest.fixture(autouse=True)
def mock_windll(monkeypatch: pytest.MonkeyPatch):
mock_windll = Mock()
monkeypatch.setattr("ctypes.windll", mock_windll, raising=False)
return mock_windll
def test_no_windll(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setitem(sys.modules, "ctypes", ModuleType("ctypes"))
assert WindowsConsole() is None
@pytest.mark.parametrize(
("method", "function"),
[
("get_std_handle", "GetStdHandle"),
("get_console_mode", "GetConsoleMode"),
("get_console_screen_buffer_info", "GetConsoleScreenBufferInfo"),
("set_console_cursor_position", "SetConsoleCursorPosition"),
("fill_console_output_attribute", "FillConsoleOutputAttribute"),
("fill_console_output_character_w", "FillConsoleOutputCharacterW"),
],
)
def test_functions(mock_windll: Mock, method: str, function: str):
windows_console = WindowsConsole()
assert isinstance(windows_console, WindowsConsole)
assert getattr(windows_console, method).method is getattr(mock_windll.kernel32, function)
def test_call_success_error(monkeypatch: pytest.MonkeyPatch, mock_windll: Mock):
windows_console = WindowsConsole()
monkeypatch.setattr(windows_console.set_console_cursor_position, "method", Mock(return_value=False))
mock_windll.kernel32.GetLastError.return_value = 87
with pytest.raises(OSError) as exc_info: # noqa: PT011
windows_console.set_console_cursor_position(123, 456)
assert str(exc_info.value) == "Error while calling kernel32.SetConsoleCursorPosition (last_error=0x57)"
@pytest.mark.parametrize(
("stream", "expected"),
[
pytest.param(None, -11, id="None"),
pytest.param(sys.stdout, -11, id="stdout"),
pytest.param(sys.stderr, -12, id="stderr"),
],
)
def test_std_handle(mock_windll: Mock, stream: TextIOWrapper | None, expected: int):
windows_console = WindowsConsole(stream)
assert mock_windll.kernel32.GetStdHandle.call_args_list == [call(expected)]
assert windows_console.handle is mock_windll.kernel32.GetStdHandle.return_value
@pytest.mark.parametrize(
("value", "success", "expected"),
[
pytest.param(0, False, False, id="error"),
pytest.param(3, True, False, id="no-virtual-terminal-processing"),
pytest.param(7, True, True, id="virtual-terminal-processing"),
],
)
def test_supports_virtual_terminal_processing(mock_windll: Mock, value: int, success: bool, expected: bool):
def fake_get_console_mode(_handle, mode):
mode.value = value
return success
mock_windll.kernel32.GetConsoleMode.side_effect = fake_get_console_mode
mock_windll.kernel32.GetLastError.return_value = 87
windows_console = WindowsConsole()
assert windows_console.supports_virtual_terminal_processing() == expected
assert mock_windll.kernel32.GetConsoleMode.call_args_list == [call(mock_windll.kernel32.GetStdHandle.return_value, ANY)]
def test_clear_line(mock_windll: Mock):
def fake_get_console_screen_buffer_info(_handle, console_screen_buffer_info):
console_screen_buffer_info.dwSize.X = 144
console_screen_buffer_info.dwSize.Y = 42
console_screen_buffer_info.dwCursorPosition.X = 20
console_screen_buffer_info.dwCursorPosition.Y = 15
console_screen_buffer_info.wAttributes = 0
return True
mock_windll.kernel32.GetConsoleScreenBufferInfo.side_effect = fake_get_console_screen_buffer_info
windows_console = WindowsConsole()
windows_console.clear_line()
assert mock_windll.kernel32.GetConsoleScreenBufferInfo.call_args_list == [
call(mock_windll.kernel32.GetStdHandle.return_value, ANY),
]
assert mock_windll.kernel32.FillConsoleOutputCharacterW.call_args_list == [
call(
mock_windll.kernel32.GetStdHandle.return_value,
EqSimpleCData(WCHAR(" ")),
EqSimpleCData(DWORD(144)),
EqStructure(COORD(0, 15)),
EqSimpleCData(DWORD(0)),
),
]
assert mock_windll.kernel32.FillConsoleOutputAttribute.call_args_list == [
call(
mock_windll.kernel32.GetStdHandle.return_value,
EqSimpleCData(WORD(0)),
EqSimpleCData(DWORD(144)),
EqStructure(COORD(0, 15)),
EqSimpleCData(DWORD(0)),
),
]
assert mock_windll.kernel32.SetConsoleCursorPosition.call_args_list == [
call(
mock_windll.kernel32.GetStdHandle.return_value,
EqStructure(COORD(0, 15)),
),
]
|