| 
									
										
										
										
											2024-05-31 17:18:28 +02:00
										 |  |  | import sys | 
					
						
							|  |  |  | import unittest | 
					
						
							| 
									
										
										
										
											2024-06-01 23:21:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-03 18:15:37 +02:00
										 |  |  | if sys.platform != "win32": | 
					
						
							| 
									
										
										
										
											2024-06-01 23:21:13 +02:00
										 |  |  |     raise unittest.SkipTest("test only relevant on win32") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import itertools | 
					
						
							| 
									
										
										
										
											2024-05-31 17:18:28 +02:00
										 |  |  | from functools import partial | 
					
						
							|  |  |  | from typing import Iterable | 
					
						
							|  |  |  | from unittest import TestCase | 
					
						
							|  |  |  | from unittest.mock import MagicMock, call | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from .support import handle_all_events, code_to_events | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | try: | 
					
						
							|  |  |  |     from _pyrepl.console import Event, Console | 
					
						
							|  |  |  |     from _pyrepl.windows_console import ( | 
					
						
							|  |  |  |         WindowsConsole, | 
					
						
							|  |  |  |         MOVE_LEFT, | 
					
						
							|  |  |  |         MOVE_RIGHT, | 
					
						
							|  |  |  |         MOVE_UP, | 
					
						
							|  |  |  |         MOVE_DOWN, | 
					
						
							|  |  |  |         ERASE_IN_LINE, | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class WindowsConsoleTests(TestCase): | 
					
						
							|  |  |  |     def console(self, events, **kwargs) -> Console: | 
					
						
							|  |  |  |         console = WindowsConsole() | 
					
						
							|  |  |  |         console.get_event = MagicMock(side_effect=events) | 
					
						
							|  |  |  |         console._scroll = MagicMock() | 
					
						
							|  |  |  |         console._hide_cursor = MagicMock() | 
					
						
							|  |  |  |         console._show_cursor = MagicMock() | 
					
						
							|  |  |  |         console._getscrollbacksize = MagicMock(42) | 
					
						
							|  |  |  |         console.out = MagicMock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         height = kwargs.get("height", 25) | 
					
						
							|  |  |  |         width = kwargs.get("width", 80) | 
					
						
							|  |  |  |         console.getheightwidth = MagicMock(side_effect=lambda: (height, width)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.prepare() | 
					
						
							|  |  |  |         for key, val in kwargs.items(): | 
					
						
							|  |  |  |             setattr(console, key, val) | 
					
						
							|  |  |  |         return console | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_events(self, events: Iterable[Event], **kwargs): | 
					
						
							|  |  |  |         return handle_all_events(events, partial(self.console, **kwargs)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_events_narrow(self, events): | 
					
						
							|  |  |  |         return self.handle_events(events, width=5) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_events_short(self, events): | 
					
						
							|  |  |  |         return self.handle_events(events, height=1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_events_height_3(self, events): | 
					
						
							|  |  |  |         return self.handle_events(events, height=3) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_simple_addition(self): | 
					
						
							|  |  |  |         code = "12+34" | 
					
						
							|  |  |  |         events = code_to_events(code) | 
					
						
							|  |  |  |         _, con = self.handle_events(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"1") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"2") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"+") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"3") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"4") | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_wrap(self): | 
					
						
							|  |  |  |         code = "12+34" | 
					
						
							|  |  |  |         events = code_to_events(code) | 
					
						
							|  |  |  |         _, con = self.handle_events_narrow(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"1") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"2") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"+") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"3") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"\\") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"\n") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"4") | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_resize_wider(self): | 
					
						
							|  |  |  |         code = "1234567890" | 
					
						
							|  |  |  |         events = code_to_events(code) | 
					
						
							|  |  |  |         reader, console = self.handle_events_narrow(events) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.height = 20 | 
					
						
							|  |  |  |         console.width = 80 | 
					
						
							|  |  |  |         console.getheightwidth = MagicMock(lambda _: (20, 80)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def same_reader(_): | 
					
						
							|  |  |  |             return reader | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def same_console(events): | 
					
						
							|  |  |  |             console.get_event = MagicMock(side_effect=events) | 
					
						
							|  |  |  |             return console | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         _, con = handle_all_events( | 
					
						
							|  |  |  |             [Event(evt="resize", data=None)], | 
					
						
							|  |  |  |             prepare_reader=same_reader, | 
					
						
							|  |  |  |             prepare_console=same_console, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_right(2)) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_up(2)) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"567890") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_resize_narrower(self): | 
					
						
							|  |  |  |         code = "1234567890" | 
					
						
							|  |  |  |         events = code_to_events(code) | 
					
						
							|  |  |  |         reader, console = self.handle_events(events) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.height = 20 | 
					
						
							|  |  |  |         console.width = 4 | 
					
						
							|  |  |  |         console.getheightwidth = MagicMock(lambda _: (20, 4)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def same_reader(_): | 
					
						
							|  |  |  |             return reader | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def same_console(events): | 
					
						
							|  |  |  |             console.get_event = MagicMock(side_effect=events) | 
					
						
							|  |  |  |             return console | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         _, con = handle_all_events( | 
					
						
							|  |  |  |             [Event(evt="resize", data=None)], | 
					
						
							|  |  |  |             prepare_reader=same_reader, | 
					
						
							|  |  |  |             prepare_console=same_console, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"456\\") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"789\\") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_cursor_left(self): | 
					
						
							|  |  |  |         code = "1" | 
					
						
							|  |  |  |         events = itertools.chain( | 
					
						
							|  |  |  |             code_to_events(code), | 
					
						
							|  |  |  |             [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         _, con = self.handle_events(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_left()) | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_cursor_left_right(self): | 
					
						
							|  |  |  |         code = "1" | 
					
						
							|  |  |  |         events = itertools.chain( | 
					
						
							|  |  |  |             code_to_events(code), | 
					
						
							|  |  |  |             [ | 
					
						
							|  |  |  |                 Event(evt="key", data="left", raw=bytearray(b"\x1bOD")), | 
					
						
							|  |  |  |                 Event(evt="key", data="right", raw=bytearray(b"\x1bOC")), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         _, con = self.handle_events(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_left()) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_right()) | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_cursor_up(self): | 
					
						
							|  |  |  |         code = "1\n2+3" | 
					
						
							|  |  |  |         events = itertools.chain( | 
					
						
							|  |  |  |             code_to_events(code), | 
					
						
							|  |  |  |             [Event(evt="key", data="up", raw=bytearray(b"\x1bOA"))], | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         _, con = self.handle_events(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_up()) | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_cursor_up_down(self): | 
					
						
							|  |  |  |         code = "1\n2+3" | 
					
						
							|  |  |  |         events = itertools.chain( | 
					
						
							|  |  |  |             code_to_events(code), | 
					
						
							|  |  |  |             [ | 
					
						
							|  |  |  |                 Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), | 
					
						
							|  |  |  |                 Event(evt="key", data="down", raw=bytearray(b"\x1bOB")), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         _, con = self.handle_events(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_up()) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_down()) | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_cursor_back_write(self): | 
					
						
							|  |  |  |         events = itertools.chain( | 
					
						
							|  |  |  |             code_to_events("1"), | 
					
						
							|  |  |  |             [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], | 
					
						
							|  |  |  |             code_to_events("2"), | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         _, con = self.handle_events(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"1") | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_left()) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(b"21") | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_multiline_function_move_up_short_terminal(self): | 
					
						
							|  |  |  |         # fmt: off | 
					
						
							|  |  |  |         code = ( | 
					
						
							|  |  |  |             "def f():\n" | 
					
						
							|  |  |  |             "  foo" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         # fmt: on | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         events = itertools.chain( | 
					
						
							|  |  |  |             code_to_events(code), | 
					
						
							|  |  |  |             [ | 
					
						
							|  |  |  |                 Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), | 
					
						
							|  |  |  |                 Event(evt="scroll", data=None), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         _, con = self.handle_events_short(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_left(5)) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_up()) | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_multiline_function_move_up_down_short_terminal(self): | 
					
						
							|  |  |  |         # fmt: off | 
					
						
							|  |  |  |         code = ( | 
					
						
							|  |  |  |             "def f():\n" | 
					
						
							|  |  |  |             "  foo" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         # fmt: on | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         events = itertools.chain( | 
					
						
							|  |  |  |             code_to_events(code), | 
					
						
							|  |  |  |             [ | 
					
						
							|  |  |  |                 Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), | 
					
						
							|  |  |  |                 Event(evt="scroll", data=None), | 
					
						
							|  |  |  |                 Event(evt="key", data="down", raw=bytearray(b"\x1bOB")), | 
					
						
							|  |  |  |                 Event(evt="scroll", data=None), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         _, con = self.handle_events_short(events) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.move_left(8)) | 
					
						
							|  |  |  |         con.out.write.assert_any_call(self.erase_in_line()) | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_resize_bigger_on_multiline_function(self): | 
					
						
							|  |  |  |         # fmt: off | 
					
						
							|  |  |  |         code = ( | 
					
						
							|  |  |  |             "def f():\n" | 
					
						
							|  |  |  |             "  foo" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         # fmt: on | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         events = itertools.chain(code_to_events(code)) | 
					
						
							|  |  |  |         reader, console = self.handle_events_short(events) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.height = 2 | 
					
						
							|  |  |  |         console.getheightwidth = MagicMock(lambda _: (2, 80)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def same_reader(_): | 
					
						
							|  |  |  |             return reader | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def same_console(events): | 
					
						
							|  |  |  |             console.get_event = MagicMock(side_effect=events) | 
					
						
							|  |  |  |             return console | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         _, con = handle_all_events( | 
					
						
							|  |  |  |             [Event(evt="resize", data=None)], | 
					
						
							|  |  |  |             prepare_reader=same_reader, | 
					
						
							|  |  |  |             prepare_console=same_console, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         con.out.write.assert_has_calls( | 
					
						
							|  |  |  |             [ | 
					
						
							|  |  |  |                 call(self.move_left(5)), | 
					
						
							|  |  |  |                 call(self.move_up()), | 
					
						
							|  |  |  |                 call(b"def f():"), | 
					
						
							|  |  |  |                 call(self.move_left(3)), | 
					
						
							|  |  |  |                 call(self.move_down()), | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         console.restore() | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_resize_smaller_on_multiline_function(self): | 
					
						
							|  |  |  |         # fmt: off | 
					
						
							|  |  |  |         code = ( | 
					
						
							|  |  |  |             "def f():\n" | 
					
						
							|  |  |  |             "  foo" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         # fmt: on | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         events = itertools.chain(code_to_events(code)) | 
					
						
							|  |  |  |         reader, console = self.handle_events_height_3(events) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.height = 1 | 
					
						
							|  |  |  |         console.getheightwidth = MagicMock(lambda _: (1, 80)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def same_reader(_): | 
					
						
							|  |  |  |             return reader | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def same_console(events): | 
					
						
							|  |  |  |             console.get_event = MagicMock(side_effect=events) | 
					
						
							|  |  |  |             return console | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         _, con = handle_all_events( | 
					
						
							|  |  |  |             [Event(evt="resize", data=None)], | 
					
						
							|  |  |  |             prepare_reader=same_reader, | 
					
						
							|  |  |  |             prepare_console=same_console, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         con.out.write.assert_has_calls( | 
					
						
							|  |  |  |             [ | 
					
						
							|  |  |  |                 call(self.move_left(5)), | 
					
						
							|  |  |  |                 call(self.move_up()), | 
					
						
							|  |  |  |                 call(self.erase_in_line()), | 
					
						
							|  |  |  |                 call(b"  foo"), | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         console.restore() | 
					
						
							|  |  |  |         con.restore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def move_up(self, lines=1): | 
					
						
							|  |  |  |         return MOVE_UP.format(lines).encode("utf8") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def move_down(self, lines=1): | 
					
						
							|  |  |  |         return MOVE_DOWN.format(lines).encode("utf8") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def move_left(self, cols=1): | 
					
						
							|  |  |  |         return MOVE_LEFT.format(cols).encode("utf8") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def move_right(self, cols=1): | 
					
						
							|  |  |  |         return MOVE_RIGHT.format(cols).encode("utf8") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def erase_in_line(self): | 
					
						
							|  |  |  |         return ERASE_IN_LINE.encode("utf8") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     unittest.main() |