mirror of
https://github.com/python/cpython.git
synced 2025-08-31 14:07:50 +00:00
gh-111201: Support pyrepl on Windows (#119559)
Co-authored-by: Anthony Shaw <anthony.p.shaw@gmail.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
13a5fdc72f
commit
0d07182821
15 changed files with 1020 additions and 49 deletions
|
@ -1,12 +1,14 @@
|
|||
import os
|
||||
import sys
|
||||
from test.support import requires, load_package_tests
|
||||
from test.support.import_helper import import_module
|
||||
|
||||
# Optionally test pyrepl. This currently requires that the
|
||||
# 'curses' resource be given on the regrtest command line using the -u
|
||||
# option. Additionally, we need to attempt to import curses and readline.
|
||||
requires("curses")
|
||||
curses = import_module("curses")
|
||||
if sys.platform != "win32":
|
||||
# On non-Windows platforms, testing pyrepl currently requires that the
|
||||
# 'curses' resource be given on the regrtest command line using the -u
|
||||
# option. Additionally, we need to attempt to import curses and readline.
|
||||
requires("curses")
|
||||
curses = import_module("curses")
|
||||
|
||||
|
||||
def load_tests(*args):
|
||||
|
|
|
@ -55,7 +55,7 @@ def prepare_reader(console: Console, **kwargs):
|
|||
return reader
|
||||
|
||||
|
||||
def prepare_console(events: Iterable[Event], **kwargs):
|
||||
def prepare_console(events: Iterable[Event], **kwargs) -> MagicMock | Console:
|
||||
console = MagicMock()
|
||||
console.get_event.side_effect = events
|
||||
console.height = 100
|
||||
|
|
|
@ -508,14 +508,15 @@ class TestPyReplCompleter(TestCase):
|
|||
reader = ReadlineAlikeReader(console=console, config=config)
|
||||
return reader
|
||||
|
||||
@patch("rlcompleter._readline_available", False)
|
||||
def test_simple_completion(self):
|
||||
events = code_to_events("os.geten\t\n")
|
||||
events = code_to_events("os.getpid\t\n")
|
||||
|
||||
namespace = {"os": os}
|
||||
reader = self.prepare_reader(events, namespace)
|
||||
|
||||
output = multiline_input(reader, namespace)
|
||||
self.assertEqual(output, "os.getenv")
|
||||
self.assertEqual(output, "os.getpid()")
|
||||
|
||||
def test_completion_with_many_options(self):
|
||||
# Test with something that initially displays many options
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import itertools
|
||||
import sys
|
||||
import unittest
|
||||
from functools import partial
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, call, patch, ANY
|
||||
|
||||
from .support import handle_all_events, code_to_events
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.unix_console import UnixConsole
|
||||
|
||||
try:
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.unix_console import UnixConsole
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def unix_console(events, **kwargs):
|
||||
console = UnixConsole()
|
||||
|
@ -67,6 +71,7 @@ TERM_CAPABILITIES = {
|
|||
}
|
||||
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "No Unix event queue on Windows")
|
||||
@patch("_pyrepl.curses.tigetstr", lambda s: TERM_CAPABILITIES.get(s))
|
||||
@patch(
|
||||
"_pyrepl.curses.tparm",
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import tempfile
|
||||
import unittest
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.unix_eventqueue import EventQueue
|
||||
|
||||
try:
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.unix_eventqueue import EventQueue
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "No Unix event queue on Windows")
|
||||
@patch("_pyrepl.curses.tigetstr", lambda x: b"")
|
||||
class TestUnixEventQueue(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
|
331
Lib/test/test_pyrepl/test_windows_console.py
Normal file
331
Lib/test/test_pyrepl/test_windows_console.py
Normal file
|
@ -0,0 +1,331 @@
|
|||
import itertools
|
||||
import sys
|
||||
import unittest
|
||||
from _pyrepl.console import Event, Console
|
||||
from _pyrepl.windows_console import (
|
||||
MOVE_LEFT,
|
||||
MOVE_RIGHT,
|
||||
MOVE_UP,
|
||||
MOVE_DOWN,
|
||||
ERASE_IN_LINE,
|
||||
)
|
||||
from functools import partial
|
||||
from typing import Iterable
|
||||
from unittest import TestCase, main
|
||||
from unittest.mock import MagicMock, call, patch, ANY
|
||||
|
||||
from .support import handle_all_events, code_to_events
|
||||
|
||||
try:
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.windows_console import WindowsConsole
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@unittest.skipIf(sys.platform != "win32", "Test class specifically for Windows")
|
||||
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()
|
Loading…
Add table
Add a link
Reference in a new issue