Todo: 集成多平台 解决因SaiNiu线程抢占资源问题 本地提交测试环境打包 和 正式打包脚本与正式环境打包bat 提交Python32环境包 改进多日志文件生成情况修改打包日志细节
This commit is contained in:
15
Utils/PythonNew32/Lib/test/test_pyrepl/__init__.py
Normal file
15
Utils/PythonNew32/Lib/test/test_pyrepl/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
import sys
|
||||
from test.support import requires, load_package_tests
|
||||
from test.support.import_helper import import_module
|
||||
|
||||
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):
|
||||
return load_package_tests(os.path.dirname(__file__), *args)
|
||||
4
Utils/PythonNew32/Lib/test/test_pyrepl/__main__.py
Normal file
4
Utils/PythonNew32/Lib/test/test_pyrepl/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
import unittest
|
||||
from test.test_pyrepl import load_tests
|
||||
|
||||
unittest.main()
|
||||
175
Utils/PythonNew32/Lib/test/test_pyrepl/support.py
Normal file
175
Utils/PythonNew32/Lib/test/test_pyrepl/support.py
Normal file
@@ -0,0 +1,175 @@
|
||||
import os
|
||||
from code import InteractiveConsole
|
||||
from functools import partial
|
||||
from typing import Iterable
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from _pyrepl.console import Console, Event
|
||||
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
|
||||
from _pyrepl.simple_interact import _strip_final_indent
|
||||
from _pyrepl.utils import unbracket, ANSI_ESCAPE_SEQUENCE
|
||||
|
||||
|
||||
class ScreenEqualMixin:
|
||||
def assert_screen_equal(
|
||||
self, reader: ReadlineAlikeReader, expected: str, clean: bool = False
|
||||
):
|
||||
actual = clean_screen(reader) if clean else reader.screen
|
||||
expected = expected.split("\n")
|
||||
self.assertListEqual(actual, expected)
|
||||
|
||||
|
||||
def multiline_input(reader: ReadlineAlikeReader, namespace: dict | None = None):
|
||||
saved = reader.more_lines
|
||||
try:
|
||||
reader.more_lines = partial(more_lines, namespace=namespace)
|
||||
reader.ps1 = reader.ps2 = ">>> "
|
||||
reader.ps3 = reader.ps4 = "... "
|
||||
return reader.readline()
|
||||
finally:
|
||||
reader.more_lines = saved
|
||||
reader.paste_mode = False
|
||||
|
||||
|
||||
def more_lines(text: str, namespace: dict | None = None):
|
||||
if namespace is None:
|
||||
namespace = {}
|
||||
src = _strip_final_indent(text)
|
||||
console = InteractiveConsole(namespace, filename="<stdin>")
|
||||
try:
|
||||
code = console.compile(src, "<stdin>", "single")
|
||||
except (OverflowError, SyntaxError, ValueError):
|
||||
return False
|
||||
else:
|
||||
return code is None
|
||||
|
||||
|
||||
def code_to_events(code: str):
|
||||
for c in code:
|
||||
yield Event(evt="key", data=c, raw=bytearray(c.encode("utf-8")))
|
||||
|
||||
|
||||
def clean_screen(reader: ReadlineAlikeReader) -> list[str]:
|
||||
"""Cleans color and console characters out of a screen output.
|
||||
|
||||
This is useful for screen testing, it increases the test readability since
|
||||
it strips out all the unreadable side of the screen.
|
||||
"""
|
||||
output = []
|
||||
for line in reader.screen:
|
||||
line = unbracket(line, including_content=True)
|
||||
line = ANSI_ESCAPE_SEQUENCE.sub("", line)
|
||||
for prefix in (reader.ps1, reader.ps2, reader.ps3, reader.ps4):
|
||||
if line.startswith(prefix):
|
||||
line = line[len(prefix):]
|
||||
break
|
||||
output.append(line)
|
||||
return output
|
||||
|
||||
|
||||
def prepare_reader(console: Console, **kwargs):
|
||||
config = ReadlineConfig(readline_completer=kwargs.pop("readline_completer", None))
|
||||
reader = ReadlineAlikeReader(console=console, config=config)
|
||||
reader.more_lines = partial(more_lines, namespace=None)
|
||||
reader.paste_mode = True # Avoid extra indents
|
||||
|
||||
def get_prompt(lineno, cursor_on_line) -> str:
|
||||
return ""
|
||||
|
||||
reader.get_prompt = get_prompt # Remove prompt for easier calculations of (x, y)
|
||||
|
||||
for key, val in kwargs.items():
|
||||
setattr(reader, key, val)
|
||||
|
||||
return reader
|
||||
|
||||
|
||||
def prepare_console(events: Iterable[Event], **kwargs) -> MagicMock | Console:
|
||||
console = MagicMock()
|
||||
console.get_event.side_effect = events
|
||||
console.height = 100
|
||||
console.width = 80
|
||||
for key, val in kwargs.items():
|
||||
setattr(console, key, val)
|
||||
return console
|
||||
|
||||
|
||||
def handle_all_events(
|
||||
events, prepare_console=prepare_console, prepare_reader=prepare_reader
|
||||
):
|
||||
console = prepare_console(events)
|
||||
reader = prepare_reader(console)
|
||||
try:
|
||||
while True:
|
||||
reader.handle1()
|
||||
except StopIteration:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return reader, console
|
||||
|
||||
|
||||
handle_events_narrow_console = partial(
|
||||
handle_all_events,
|
||||
prepare_console=partial(prepare_console, width=10),
|
||||
)
|
||||
|
||||
reader_no_colors = partial(prepare_reader, can_colorize=False)
|
||||
reader_force_colors = partial(prepare_reader, can_colorize=True)
|
||||
|
||||
|
||||
class FakeConsole(Console):
|
||||
def __init__(self, events, encoding="utf-8") -> None:
|
||||
self.events = iter(events)
|
||||
self.encoding = encoding
|
||||
self.screen = []
|
||||
self.height = 100
|
||||
self.width = 80
|
||||
|
||||
def get_event(self, block: bool = True) -> Event | None:
|
||||
return next(self.events)
|
||||
|
||||
def getpending(self) -> Event:
|
||||
return self.get_event(block=False)
|
||||
|
||||
def getheightwidth(self) -> tuple[int, int]:
|
||||
return self.height, self.width
|
||||
|
||||
def refresh(self, screen: list[str], xy: tuple[int, int]) -> None:
|
||||
pass
|
||||
|
||||
def prepare(self) -> None:
|
||||
pass
|
||||
|
||||
def restore(self) -> None:
|
||||
pass
|
||||
|
||||
def move_cursor(self, x: int, y: int) -> None:
|
||||
pass
|
||||
|
||||
def set_cursor_vis(self, visible: bool) -> None:
|
||||
pass
|
||||
|
||||
def push_char(self, char: int | bytes) -> None:
|
||||
pass
|
||||
|
||||
def beep(self) -> None:
|
||||
pass
|
||||
|
||||
def clear(self) -> None:
|
||||
pass
|
||||
|
||||
def finish(self) -> None:
|
||||
pass
|
||||
|
||||
def flushoutput(self) -> None:
|
||||
pass
|
||||
|
||||
def forgetinput(self) -> None:
|
||||
pass
|
||||
|
||||
def wait(self, timeout: float | None = None) -> bool:
|
||||
return True
|
||||
|
||||
def repaint(self) -> None:
|
||||
pass
|
||||
192
Utils/PythonNew32/Lib/test/test_pyrepl/test_eventqueue.py
Normal file
192
Utils/PythonNew32/Lib/test/test_pyrepl/test_eventqueue.py
Normal file
@@ -0,0 +1,192 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
from test import support
|
||||
|
||||
try:
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl import base_eventqueue
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from _pyrepl import unix_eventqueue
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from _pyrepl import windows_eventqueue
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class EventQueueTestBase:
|
||||
"""OS-independent mixin"""
|
||||
def make_eventqueue(self) -> base_eventqueue.BaseEventQueue:
|
||||
raise NotImplementedError()
|
||||
|
||||
def test_get(self):
|
||||
eq = self.make_eventqueue()
|
||||
event = Event("key", "a", b"a")
|
||||
eq.insert(event)
|
||||
self.assertEqual(eq.get(), event)
|
||||
|
||||
def test_empty(self):
|
||||
eq = self.make_eventqueue()
|
||||
self.assertTrue(eq.empty())
|
||||
eq.insert(Event("key", "a", b"a"))
|
||||
self.assertFalse(eq.empty())
|
||||
|
||||
def test_flush_buf(self):
|
||||
eq = self.make_eventqueue()
|
||||
eq.buf.extend(b"test")
|
||||
self.assertEqual(eq.flush_buf(), b"test")
|
||||
self.assertEqual(eq.buf, bytearray())
|
||||
|
||||
def test_insert(self):
|
||||
eq = self.make_eventqueue()
|
||||
event = Event("key", "a", b"a")
|
||||
eq.insert(event)
|
||||
self.assertEqual(eq.events[0], event)
|
||||
|
||||
@patch("_pyrepl.base_eventqueue.keymap")
|
||||
def test_push_with_key_in_keymap(self, mock_keymap):
|
||||
mock_keymap.compile_keymap.return_value = {"a": "b"}
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {b"a": "b"}
|
||||
eq.push(b"a")
|
||||
mock_keymap.compile_keymap.assert_called()
|
||||
self.assertEqual(eq.events[0].evt, "key")
|
||||
self.assertEqual(eq.events[0].data, "b")
|
||||
|
||||
@patch("_pyrepl.base_eventqueue.keymap")
|
||||
def test_push_without_key_in_keymap(self, mock_keymap):
|
||||
mock_keymap.compile_keymap.return_value = {"a": "b"}
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {b"c": "d"}
|
||||
eq.push(b"a")
|
||||
mock_keymap.compile_keymap.assert_called()
|
||||
self.assertEqual(eq.events[0].evt, "key")
|
||||
self.assertEqual(eq.events[0].data, "a")
|
||||
|
||||
@patch("_pyrepl.base_eventqueue.keymap")
|
||||
def test_push_with_keymap_in_keymap(self, mock_keymap):
|
||||
mock_keymap.compile_keymap.return_value = {"a": "b"}
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {b"a": {b"b": "c"}}
|
||||
eq.push(b"a")
|
||||
mock_keymap.compile_keymap.assert_called()
|
||||
self.assertTrue(eq.empty())
|
||||
eq.push(b"b")
|
||||
self.assertEqual(eq.events[0].evt, "key")
|
||||
self.assertEqual(eq.events[0].data, "c")
|
||||
eq.push(b"d")
|
||||
self.assertEqual(eq.events[1].evt, "key")
|
||||
self.assertEqual(eq.events[1].data, "d")
|
||||
|
||||
@patch("_pyrepl.base_eventqueue.keymap")
|
||||
def test_push_with_keymap_in_keymap_and_escape(self, mock_keymap):
|
||||
mock_keymap.compile_keymap.return_value = {"a": "b"}
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {b"a": {b"b": "c"}}
|
||||
eq.push(b"a")
|
||||
mock_keymap.compile_keymap.assert_called()
|
||||
self.assertTrue(eq.empty())
|
||||
eq.flush_buf()
|
||||
eq.push(b"\033")
|
||||
self.assertEqual(eq.events[0].evt, "key")
|
||||
self.assertEqual(eq.events[0].data, "\033")
|
||||
eq.push(b"b")
|
||||
self.assertEqual(eq.events[1].evt, "key")
|
||||
self.assertEqual(eq.events[1].data, "b")
|
||||
|
||||
def test_push_special_key(self):
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {}
|
||||
eq.push(b"\x1b")
|
||||
eq.push(b"[")
|
||||
eq.push(b"A")
|
||||
self.assertEqual(eq.events[0].evt, "key")
|
||||
self.assertEqual(eq.events[0].data, "\x1b")
|
||||
|
||||
def test_push_unrecognized_escape_sequence(self):
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {}
|
||||
eq.push(b"\x1b")
|
||||
eq.push(b"[")
|
||||
eq.push(b"Z")
|
||||
self.assertEqual(len(eq.events), 3)
|
||||
self.assertEqual(eq.events[0].evt, "key")
|
||||
self.assertEqual(eq.events[0].data, "\x1b")
|
||||
self.assertEqual(eq.events[1].evt, "key")
|
||||
self.assertEqual(eq.events[1].data, "[")
|
||||
self.assertEqual(eq.events[2].evt, "key")
|
||||
self.assertEqual(eq.events[2].data, "Z")
|
||||
|
||||
def test_push_unicode_character_as_str(self):
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {}
|
||||
with self.assertRaises(AssertionError):
|
||||
eq.push("ч")
|
||||
with self.assertRaises(AssertionError):
|
||||
eq.push("ñ")
|
||||
|
||||
def test_push_unicode_character_two_bytes(self):
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {}
|
||||
|
||||
encoded = "ч".encode(eq.encoding, "replace")
|
||||
self.assertEqual(len(encoded), 2)
|
||||
|
||||
eq.push(encoded[0])
|
||||
e = eq.get()
|
||||
self.assertIsNone(e)
|
||||
|
||||
eq.push(encoded[1])
|
||||
e = eq.get()
|
||||
self.assertEqual(e.evt, "key")
|
||||
self.assertEqual(e.data, "ч")
|
||||
|
||||
def test_push_single_chars_and_unicode_character_as_str(self):
|
||||
eq = self.make_eventqueue()
|
||||
eq.keymap = {}
|
||||
|
||||
def _event(evt, data, raw=None):
|
||||
r = raw if raw is not None else data.encode(eq.encoding)
|
||||
e = Event(evt, data, r)
|
||||
return e
|
||||
|
||||
def _push(keys):
|
||||
for k in keys:
|
||||
eq.push(k)
|
||||
|
||||
self.assertIsInstance("ñ", str)
|
||||
|
||||
# If an exception happens during push, the existing events must be
|
||||
# preserved and we can continue to push.
|
||||
_push(b"b")
|
||||
with self.assertRaises(AssertionError):
|
||||
_push("ñ")
|
||||
_push(b"a")
|
||||
|
||||
self.assertEqual(eq.get(), _event("key", "b"))
|
||||
self.assertEqual(eq.get(), _event("key", "a"))
|
||||
|
||||
|
||||
@unittest.skipIf(support.MS_WINDOWS, "No Unix event queue on Windows")
|
||||
class TestUnixEventQueue(EventQueueTestBase, unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.enterContext(patch("_pyrepl.curses.tigetstr", lambda x: b""))
|
||||
self.file = tempfile.TemporaryFile()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.file.close()
|
||||
|
||||
def make_eventqueue(self) -> base_eventqueue.BaseEventQueue:
|
||||
return unix_eventqueue.EventQueue(self.file.fileno(), "utf-8")
|
||||
|
||||
|
||||
@unittest.skipUnless(support.MS_WINDOWS, "No Windows event queue on Unix")
|
||||
class TestWindowsEventQueue(EventQueueTestBase, unittest.TestCase):
|
||||
def make_eventqueue(self) -> base_eventqueue.BaseEventQueue:
|
||||
return windows_eventqueue.EventQueue("utf-8")
|
||||
102
Utils/PythonNew32/Lib/test/test_pyrepl/test_input.py
Normal file
102
Utils/PythonNew32/Lib/test/test_pyrepl/test_input.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import unittest
|
||||
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.input import KeymapTranslator
|
||||
|
||||
|
||||
class KeymapTranslatorTests(unittest.TestCase):
|
||||
def test_push_single_key(self):
|
||||
keymap = [("a", "command_a")]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt = Event("key", "a")
|
||||
translator.push(evt)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, ("command_a", ["a"]))
|
||||
|
||||
def test_push_multiple_keys(self):
|
||||
keymap = [("ab", "command_ab")]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt1 = Event("key", "a")
|
||||
evt2 = Event("key", "b")
|
||||
translator.push(evt1)
|
||||
translator.push(evt2)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, ("command_ab", ["a", "b"]))
|
||||
|
||||
def test_push_invalid_key(self):
|
||||
keymap = [("a", "command_a")]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt = Event("key", "b")
|
||||
translator.push(evt)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, (None, ["b"]))
|
||||
|
||||
def test_push_invalid_key_with_stack(self):
|
||||
keymap = [("ab", "command_ab")]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt1 = Event("key", "a")
|
||||
evt2 = Event("key", "c")
|
||||
translator.push(evt1)
|
||||
translator.push(evt2)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, (None, ["a", "c"]))
|
||||
|
||||
def test_push_character_key(self):
|
||||
keymap = [("a", "command_a")]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt = Event("key", "a")
|
||||
translator.push(evt)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, ("command_a", ["a"]))
|
||||
|
||||
def test_push_character_key_with_stack(self):
|
||||
keymap = [("ab", "command_ab")]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt1 = Event("key", "a")
|
||||
evt2 = Event("key", "b")
|
||||
evt3 = Event("key", "c")
|
||||
translator.push(evt1)
|
||||
translator.push(evt2)
|
||||
translator.push(evt3)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, ("command_ab", ["a", "b"]))
|
||||
|
||||
def test_push_transition_key(self):
|
||||
keymap = [("a", {"b": "command_ab"})]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt1 = Event("key", "a")
|
||||
evt2 = Event("key", "b")
|
||||
translator.push(evt1)
|
||||
translator.push(evt2)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, ("command_ab", ["a", "b"]))
|
||||
|
||||
def test_push_transition_key_interrupted(self):
|
||||
keymap = [("a", {"b": "command_ab"})]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt1 = Event("key", "a")
|
||||
evt2 = Event("key", "c")
|
||||
evt3 = Event("key", "b")
|
||||
translator.push(evt1)
|
||||
translator.push(evt2)
|
||||
translator.push(evt3)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, (None, ["a", "c"]))
|
||||
|
||||
def test_push_invalid_key_with_unicode_category(self):
|
||||
keymap = [("a", "command_a")]
|
||||
translator = KeymapTranslator(keymap)
|
||||
evt = Event("key", "\u0003") # Control character
|
||||
translator.push(evt)
|
||||
result = translator.get()
|
||||
self.assertEqual(result, (None, ["\u0003"]))
|
||||
|
||||
def test_empty(self):
|
||||
keymap = [("a", "command_a")]
|
||||
translator = KeymapTranslator(keymap)
|
||||
self.assertTrue(translator.empty())
|
||||
evt = Event("key", "a")
|
||||
translator.push(evt)
|
||||
self.assertFalse(translator.empty())
|
||||
translator.get()
|
||||
self.assertTrue(translator.empty())
|
||||
275
Utils/PythonNew32/Lib/test/test_pyrepl/test_interact.py
Normal file
275
Utils/PythonNew32/Lib/test/test_pyrepl/test_interact.py
Normal file
@@ -0,0 +1,275 @@
|
||||
import contextlib
|
||||
import io
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from textwrap import dedent
|
||||
|
||||
from test.support import force_not_colorized
|
||||
|
||||
from _pyrepl.console import InteractiveColoredConsole
|
||||
from _pyrepl.simple_interact import _more_lines
|
||||
|
||||
class TestSimpleInteract(unittest.TestCase):
|
||||
def test_multiple_statements(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
class A:
|
||||
def foo(self):
|
||||
|
||||
|
||||
pass
|
||||
|
||||
class B:
|
||||
def bar(self):
|
||||
pass
|
||||
|
||||
a = 1
|
||||
a
|
||||
""")
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
f = io.StringIO()
|
||||
with (
|
||||
patch.object(InteractiveColoredConsole, "showsyntaxerror") as showsyntaxerror,
|
||||
patch.object(InteractiveColoredConsole, "runsource", wraps=console.runsource) as runsource,
|
||||
contextlib.redirect_stdout(f),
|
||||
):
|
||||
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
|
||||
self.assertFalse(more)
|
||||
showsyntaxerror.assert_not_called()
|
||||
|
||||
|
||||
def test_multiple_statements_output(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
b = 1
|
||||
b
|
||||
a = 1
|
||||
a
|
||||
""")
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stdout(f):
|
||||
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
|
||||
self.assertFalse(more)
|
||||
self.assertEqual(f.getvalue(), "1\n")
|
||||
|
||||
@force_not_colorized
|
||||
def test_multiple_statements_fail_early(self):
|
||||
console = InteractiveColoredConsole()
|
||||
code = dedent("""\
|
||||
raise Exception('foobar')
|
||||
print('spam', 'eggs', sep='&')
|
||||
""")
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stderr(f):
|
||||
console.runsource(code)
|
||||
self.assertIn('Exception: foobar', f.getvalue())
|
||||
self.assertNotIn('spam&eggs', f.getvalue())
|
||||
|
||||
def test_empty(self):
|
||||
namespace = {}
|
||||
code = ""
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stdout(f):
|
||||
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
|
||||
self.assertFalse(more)
|
||||
self.assertEqual(f.getvalue(), "")
|
||||
|
||||
def test_runsource_compiles_and_runs_code(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = "print('Hello, world!')"
|
||||
with patch.object(console, "runcode") as mock_runcode:
|
||||
console.runsource(source)
|
||||
mock_runcode.assert_called_once()
|
||||
|
||||
def test_runsource_returns_false_for_successful_compilation(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = "print('Hello, world!')"
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stdout(f):
|
||||
result = console.runsource(source)
|
||||
self.assertFalse(result)
|
||||
|
||||
@force_not_colorized
|
||||
def test_runsource_returns_false_for_failed_compilation(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = "print('Hello, world!'"
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stderr(f):
|
||||
result = console.runsource(source)
|
||||
self.assertFalse(result)
|
||||
self.assertIn('SyntaxError', f.getvalue())
|
||||
|
||||
@force_not_colorized
|
||||
def test_runsource_show_syntax_error_location(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = "def f(x, x): ..."
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stderr(f):
|
||||
result = console.runsource(source)
|
||||
self.assertFalse(result)
|
||||
r = """
|
||||
def f(x, x): ...
|
||||
^
|
||||
SyntaxError: duplicate argument 'x' in function definition"""
|
||||
self.assertIn(r, f.getvalue())
|
||||
|
||||
def test_runsource_shows_syntax_error_for_failed_compilation(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = "print('Hello, world!'"
|
||||
with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror:
|
||||
console.runsource(source)
|
||||
mock_showsyntaxerror.assert_called_once()
|
||||
source = dedent("""\
|
||||
match 1:
|
||||
case {0: _, 0j: _}:
|
||||
pass
|
||||
""")
|
||||
with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror:
|
||||
console.runsource(source)
|
||||
mock_showsyntaxerror.assert_called_once()
|
||||
|
||||
def test_runsource_survives_null_bytes(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = "\x00\n"
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
|
||||
result = console.runsource(source)
|
||||
self.assertFalse(result)
|
||||
self.assertIn("source code string cannot contain null bytes", f.getvalue())
|
||||
|
||||
def test_no_active_future(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = dedent("""\
|
||||
x: int = 1
|
||||
print(__annotations__)
|
||||
""")
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stdout(f):
|
||||
result = console.runsource(source)
|
||||
self.assertFalse(result)
|
||||
self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n")
|
||||
|
||||
def test_future_annotations(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = dedent("""\
|
||||
from __future__ import annotations
|
||||
def g(x: int): ...
|
||||
print(g.__annotations__)
|
||||
""")
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stdout(f):
|
||||
result = console.runsource(source)
|
||||
self.assertFalse(result)
|
||||
self.assertEqual(f.getvalue(), "{'x': 'int'}\n")
|
||||
|
||||
def test_future_barry_as_flufl(self):
|
||||
console = InteractiveColoredConsole()
|
||||
f = io.StringIO()
|
||||
with contextlib.redirect_stdout(f):
|
||||
result = console.runsource("from __future__ import barry_as_FLUFL\n")
|
||||
result = console.runsource("""print("black" <> 'blue')\n""")
|
||||
self.assertFalse(result)
|
||||
self.assertEqual(f.getvalue(), "True\n")
|
||||
|
||||
|
||||
class TestMoreLines(unittest.TestCase):
|
||||
def test_invalid_syntax_single_line(self):
|
||||
namespace = {}
|
||||
code = "if foo"
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
def test_empty_line(self):
|
||||
namespace = {}
|
||||
code = ""
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
def test_valid_single_statement(self):
|
||||
namespace = {}
|
||||
code = "foo = 1"
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
def test_multiline_single_assignment(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
foo = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]""")
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
def test_multiline_single_block(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
def foo():
|
||||
'''docs'''
|
||||
|
||||
return 1""")
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertTrue(_more_lines(console, code))
|
||||
|
||||
def test_multiple_statements_single_line(self):
|
||||
namespace = {}
|
||||
code = "foo = 1;bar = 2"
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
def test_multiple_statements(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
import time
|
||||
|
||||
foo = 1""")
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertTrue(_more_lines(console, code))
|
||||
|
||||
def test_multiple_blocks(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: float
|
||||
y: float""")
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertTrue(_more_lines(console, code))
|
||||
|
||||
def test_multiple_blocks_empty_newline(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: float
|
||||
y: float
|
||||
""")
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
def test_multiple_blocks_indented_newline(self):
|
||||
namespace = {}
|
||||
code = (
|
||||
"from dataclasses import dataclass\n"
|
||||
"\n"
|
||||
"@dataclass\n"
|
||||
"class Point:\n"
|
||||
" x: float\n"
|
||||
" y: float\n"
|
||||
" "
|
||||
)
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
def test_incomplete_statement(self):
|
||||
namespace = {}
|
||||
code = "if foo:"
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertTrue(_more_lines(console, code))
|
||||
120
Utils/PythonNew32/Lib/test/test_pyrepl/test_keymap.py
Normal file
120
Utils/PythonNew32/Lib/test/test_pyrepl/test_keymap.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import string
|
||||
import unittest
|
||||
|
||||
from _pyrepl.keymap import _keynames, _escapes, parse_keys, compile_keymap, KeySpecError
|
||||
|
||||
|
||||
class TestParseKeys(unittest.TestCase):
|
||||
def test_single_character(self):
|
||||
"""Ensure that single ascii characters or single digits are parsed as single characters."""
|
||||
test_cases = [(key, [key]) for key in string.ascii_letters + string.digits]
|
||||
for test_key, expected_keys in test_cases:
|
||||
with self.subTest(f"{test_key} should be parsed as {expected_keys}"):
|
||||
self.assertEqual(parse_keys(test_key), expected_keys)
|
||||
|
||||
def test_keynames(self):
|
||||
"""Ensure that keynames are parsed to their corresponding mapping.
|
||||
|
||||
A keyname is expected to be of the following form: \\<keyname> such as \\<left>
|
||||
which would get parsed as "left".
|
||||
"""
|
||||
test_cases = [(f"\\<{keyname}>", [parsed_keyname]) for keyname, parsed_keyname in _keynames.items()]
|
||||
for test_key, expected_keys in test_cases:
|
||||
with self.subTest(f"{test_key} should be parsed as {expected_keys}"):
|
||||
self.assertEqual(parse_keys(test_key), expected_keys)
|
||||
|
||||
def test_escape_sequences(self):
|
||||
"""Ensure that escaping sequences are parsed to their corresponding mapping."""
|
||||
test_cases = [(f"\\{escape}", [parsed_escape]) for escape, parsed_escape in _escapes.items()]
|
||||
for test_key, expected_keys in test_cases:
|
||||
with self.subTest(f"{test_key} should be parsed as {expected_keys}"):
|
||||
self.assertEqual(parse_keys(test_key), expected_keys)
|
||||
|
||||
def test_control_sequences(self):
|
||||
"""Ensure that supported control sequences are parsed successfully."""
|
||||
keys = ["@", "[", "]", "\\", "^", "_", "\\<space>", "\\<delete>"]
|
||||
keys.extend(string.ascii_letters)
|
||||
test_cases = [(f"\\C-{key}", chr(ord(key) & 0x1F)) for key in []]
|
||||
for test_key, expected_keys in test_cases:
|
||||
with self.subTest(f"{test_key} should be parsed as {expected_keys}"):
|
||||
self.assertEqual(parse_keys(test_key), expected_keys)
|
||||
|
||||
def test_meta_sequences(self):
|
||||
self.assertEqual(parse_keys("\\M-a"), ["\033", "a"])
|
||||
self.assertEqual(parse_keys("\\M-b"), ["\033", "b"])
|
||||
self.assertEqual(parse_keys("\\M-c"), ["\033", "c"])
|
||||
|
||||
def test_combinations(self):
|
||||
self.assertEqual(parse_keys("\\C-a\\n\\<up>"), ["\x01", "\n", "up"])
|
||||
self.assertEqual(parse_keys("\\M-a\\t\\<down>"), ["\033", "a", "\t", "down"])
|
||||
|
||||
def test_keyspec_errors(self):
|
||||
cases = [
|
||||
("\\Ca", "\\C must be followed by `-'"),
|
||||
("\\ca", "\\C must be followed by `-'"),
|
||||
("\\C-\\C-", "doubled \\C-"),
|
||||
("\\Ma", "\\M must be followed by `-'"),
|
||||
("\\ma", "\\M must be followed by `-'"),
|
||||
("\\M-\\M-", "doubled \\M-"),
|
||||
("\\<left", "unterminated \\<"),
|
||||
("\\<unsupported>", "unrecognised keyname"),
|
||||
("\\大", "unknown backslash escape"),
|
||||
("\\C-\\<backspace>", "\\C- followed by invalid key")
|
||||
]
|
||||
for test_keys, expected_err in cases:
|
||||
with self.subTest(f"{test_keys} should give error {expected_err}"):
|
||||
with self.assertRaises(KeySpecError) as e:
|
||||
parse_keys(test_keys)
|
||||
self.assertIn(expected_err, str(e.exception))
|
||||
|
||||
def test_index_errors(self):
|
||||
test_cases = ["\\", "\\C", "\\C-\\C"]
|
||||
for test_keys in test_cases:
|
||||
with self.assertRaises(IndexError):
|
||||
parse_keys(test_keys)
|
||||
|
||||
|
||||
class TestCompileKeymap(unittest.TestCase):
|
||||
def test_empty_keymap(self):
|
||||
keymap = {}
|
||||
result = compile_keymap(keymap)
|
||||
self.assertEqual(result, {})
|
||||
|
||||
def test_single_keymap(self):
|
||||
keymap = {b"a": "action"}
|
||||
result = compile_keymap(keymap)
|
||||
self.assertEqual(result, {b"a": "action"})
|
||||
|
||||
def test_nested_keymap(self):
|
||||
keymap = {b"a": {b"b": "action"}}
|
||||
result = compile_keymap(keymap)
|
||||
self.assertEqual(result, {b"a": {b"b": "action"}})
|
||||
|
||||
def test_empty_value(self):
|
||||
keymap = {b"a": {b"": "action"}}
|
||||
result = compile_keymap(keymap)
|
||||
self.assertEqual(result, {b"a": {b"": "action"}})
|
||||
|
||||
def test_multiple_empty_values(self):
|
||||
keymap = {b"a": {b"": "action1", b"b": "action2"}}
|
||||
result = compile_keymap(keymap)
|
||||
self.assertEqual(result, {b"a": {b"": "action1", b"b": "action2"}})
|
||||
|
||||
def test_multiple_keymaps(self):
|
||||
keymap = {b"a": {b"b": "action1", b"c": "action2"}}
|
||||
result = compile_keymap(keymap)
|
||||
self.assertEqual(result, {b"a": {b"b": "action1", b"c": "action2"}})
|
||||
|
||||
def test_nested_multiple_keymaps(self):
|
||||
keymap = {b"a": {b"b": {b"c": "action"}}}
|
||||
result = compile_keymap(keymap)
|
||||
self.assertEqual(result, {b"a": {b"b": {b"c": "action"}}})
|
||||
|
||||
def test_clashing_definitions(self):
|
||||
km = {b'a': 'c', b'a' + b'b': 'd'}
|
||||
with self.assertRaises(KeySpecError):
|
||||
compile_keymap(km)
|
||||
|
||||
def test_non_bytes_key(self):
|
||||
with self.assertRaises(TypeError):
|
||||
compile_keymap({123: 'a'})
|
||||
1406
Utils/PythonNew32/Lib/test/test_pyrepl/test_pyrepl.py
Normal file
1406
Utils/PythonNew32/Lib/test/test_pyrepl/test_pyrepl.py
Normal file
File diff suppressed because it is too large
Load Diff
357
Utils/PythonNew32/Lib/test/test_pyrepl/test_reader.py
Normal file
357
Utils/PythonNew32/Lib/test/test_pyrepl/test_reader.py
Normal file
@@ -0,0 +1,357 @@
|
||||
import itertools
|
||||
import functools
|
||||
import rlcompleter
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from .support import handle_all_events, handle_events_narrow_console
|
||||
from .support import ScreenEqualMixin, code_to_events
|
||||
from .support import prepare_reader, prepare_console
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.reader import Reader
|
||||
|
||||
|
||||
class TestReader(ScreenEqualMixin, TestCase):
|
||||
def test_calc_screen_wrap_simple(self):
|
||||
events = code_to_events(10 * "a")
|
||||
reader, _ = handle_events_narrow_console(events)
|
||||
self.assert_screen_equal(reader, f"{9*"a"}\\\na")
|
||||
|
||||
def test_calc_screen_wrap_wide_characters(self):
|
||||
events = code_to_events(8 * "a" + "樂")
|
||||
reader, _ = handle_events_narrow_console(events)
|
||||
self.assert_screen_equal(reader, f"{8*"a"}\\\n樂")
|
||||
|
||||
def test_calc_screen_wrap_three_lines(self):
|
||||
events = code_to_events(20 * "a")
|
||||
reader, _ = handle_events_narrow_console(events)
|
||||
self.assert_screen_equal(reader, f"{9*"a"}\\\n{9*"a"}\\\naa")
|
||||
|
||||
def test_calc_screen_prompt_handling(self):
|
||||
def prepare_reader_keep_prompts(*args, **kwargs):
|
||||
reader = prepare_reader(*args, **kwargs)
|
||||
del reader.get_prompt
|
||||
reader.ps1 = ">>> "
|
||||
reader.ps2 = ">>> "
|
||||
reader.ps3 = "... "
|
||||
reader.ps4 = ""
|
||||
reader.can_colorize = False
|
||||
reader.paste_mode = False
|
||||
return reader
|
||||
|
||||
events = code_to_events("if some_condition:\nsome_function()")
|
||||
reader, _ = handle_events_narrow_console(
|
||||
events,
|
||||
prepare_reader=prepare_reader_keep_prompts,
|
||||
)
|
||||
# fmt: off
|
||||
self.assert_screen_equal(
|
||||
reader,
|
||||
(
|
||||
">>> if so\\\n"
|
||||
"me_condit\\\n"
|
||||
"ion:\n"
|
||||
"... s\\\n"
|
||||
"ome_funct\\\n"
|
||||
"ion()"
|
||||
)
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
def test_calc_screen_wrap_three_lines_mixed_character(self):
|
||||
# fmt: off
|
||||
code = (
|
||||
"def f():\n"
|
||||
f" {8*"a"}\n"
|
||||
f" {5*"樂"}"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
events = code_to_events(code)
|
||||
reader, _ = handle_events_narrow_console(events)
|
||||
|
||||
# fmt: off
|
||||
self.assert_screen_equal(
|
||||
reader,
|
||||
(
|
||||
"def f():\n"
|
||||
f" {7*"a"}\\\n"
|
||||
"a\n"
|
||||
f" {3*"樂"}\\\n"
|
||||
"樂樂"
|
||||
),
|
||||
clean=True,
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
def test_calc_screen_backspace(self):
|
||||
events = itertools.chain(
|
||||
code_to_events("aaa"),
|
||||
[
|
||||
Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
|
||||
],
|
||||
)
|
||||
reader, _ = handle_all_events(events)
|
||||
self.assert_screen_equal(reader, "aa")
|
||||
|
||||
def test_calc_screen_wrap_removes_after_backspace(self):
|
||||
events = itertools.chain(
|
||||
code_to_events(10 * "a"),
|
||||
[
|
||||
Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
|
||||
],
|
||||
)
|
||||
reader, _ = handle_events_narrow_console(events)
|
||||
self.assert_screen_equal(reader, 9 * "a")
|
||||
|
||||
def test_calc_screen_backspace_in_second_line_after_wrap(self):
|
||||
events = itertools.chain(
|
||||
code_to_events(11 * "a"),
|
||||
[
|
||||
Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
|
||||
],
|
||||
)
|
||||
reader, _ = handle_events_narrow_console(events)
|
||||
self.assert_screen_equal(reader, f"{9*"a"}\\\na")
|
||||
|
||||
def test_setpos_for_xy_simple(self):
|
||||
events = code_to_events("11+11")
|
||||
reader, _ = handle_all_events(events)
|
||||
reader.setpos_from_xy(0, 0)
|
||||
self.assertEqual(reader.pos, 0)
|
||||
|
||||
def test_control_characters(self):
|
||||
code = 'flag = "🏳️🌈"'
|
||||
events = code_to_events(code)
|
||||
reader, _ = handle_all_events(events)
|
||||
self.assert_screen_equal(reader, 'flag = "🏳️\\u200d🌈"', clean=True)
|
||||
|
||||
def test_setpos_from_xy_multiple_lines(self):
|
||||
# fmt: off
|
||||
code = (
|
||||
"def foo():\n"
|
||||
" return 1"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
events = code_to_events(code)
|
||||
reader, _ = handle_all_events(events)
|
||||
reader.setpos_from_xy(2, 1)
|
||||
self.assertEqual(reader.pos, 13)
|
||||
|
||||
def test_setpos_from_xy_after_wrap(self):
|
||||
# fmt: off
|
||||
code = (
|
||||
"def foo():\n"
|
||||
" hello"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
events = code_to_events(code)
|
||||
reader, _ = handle_events_narrow_console(events)
|
||||
reader.setpos_from_xy(2, 2)
|
||||
self.assertEqual(reader.pos, 13)
|
||||
|
||||
def test_setpos_fromxy_in_wrapped_line(self):
|
||||
# fmt: off
|
||||
code = (
|
||||
"def foo():\n"
|
||||
" hello"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
events = code_to_events(code)
|
||||
reader, _ = handle_events_narrow_console(events)
|
||||
reader.setpos_from_xy(0, 1)
|
||||
self.assertEqual(reader.pos, 9)
|
||||
|
||||
def test_up_arrow_after_ctrl_r(self):
|
||||
events = iter(
|
||||
[
|
||||
Event(evt="key", data="\x12", raw=bytearray(b"\x12")),
|
||||
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
|
||||
]
|
||||
)
|
||||
|
||||
reader, _ = handle_all_events(events)
|
||||
self.assert_screen_equal(reader, "")
|
||||
|
||||
def test_newline_within_block_trailing_whitespace(self):
|
||||
# fmt: off
|
||||
code = (
|
||||
"def foo():\n"
|
||||
"a = 1\n"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
events = itertools.chain(
|
||||
code_to_events(code),
|
||||
[
|
||||
# go to the end of the first line
|
||||
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
|
||||
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
|
||||
Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
|
||||
# new lines in-block shouldn't terminate the block
|
||||
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||
# end of line 2
|
||||
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
|
||||
Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
|
||||
# a double new line in-block should terminate the block
|
||||
# even if its followed by whitespace
|
||||
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||
],
|
||||
)
|
||||
|
||||
no_paste_reader = functools.partial(prepare_reader, paste_mode=False)
|
||||
reader, _ = handle_all_events(events, prepare_reader=no_paste_reader)
|
||||
|
||||
expected = (
|
||||
"def foo():\n"
|
||||
" \n"
|
||||
" \n"
|
||||
" a = 1\n"
|
||||
" \n"
|
||||
" " # HistoricalReader will trim trailing whitespace
|
||||
)
|
||||
self.assert_screen_equal(reader, expected, clean=True)
|
||||
self.assertTrue(reader.finished)
|
||||
|
||||
def test_input_hook_is_called_if_set(self):
|
||||
input_hook = MagicMock()
|
||||
|
||||
def _prepare_console(events):
|
||||
console = MagicMock()
|
||||
console.get_event.side_effect = events
|
||||
console.height = 100
|
||||
console.width = 80
|
||||
console.input_hook = input_hook
|
||||
return console
|
||||
|
||||
events = code_to_events("a")
|
||||
reader, _ = handle_all_events(events, prepare_console=_prepare_console)
|
||||
|
||||
self.assertEqual(len(input_hook.mock_calls), 4)
|
||||
|
||||
def test_keyboard_interrupt_clears_screen(self):
|
||||
namespace = {"itertools": itertools}
|
||||
code = "import itertools\nitertools."
|
||||
events = itertools.chain(
|
||||
code_to_events(code),
|
||||
[
|
||||
# Two tabs for completion
|
||||
Event(evt="key", data="\t", raw=bytearray(b"\t")),
|
||||
Event(evt="key", data="\t", raw=bytearray(b"\t")),
|
||||
Event(evt="key", data="\x03", raw=bytearray(b"\x03")), # Ctrl-C
|
||||
],
|
||||
)
|
||||
console = prepare_console(events)
|
||||
reader = prepare_reader(
|
||||
console,
|
||||
readline_completer=rlcompleter.Completer(namespace).complete,
|
||||
)
|
||||
try:
|
||||
# we're not using handle_all_events() here to be able to
|
||||
# follow the KeyboardInterrupt sequence of events. Normally this
|
||||
# happens in simple_interact.run_multiline_interactive_console.
|
||||
while True:
|
||||
reader.handle1()
|
||||
except KeyboardInterrupt:
|
||||
# at this point the completions are still visible
|
||||
self.assertTrue(len(reader.screen) > 2)
|
||||
reader.refresh()
|
||||
# after the refresh, they are gone
|
||||
self.assertEqual(len(reader.screen), 2)
|
||||
self.assert_screen_equal(reader, code, clean=True)
|
||||
else:
|
||||
self.fail("KeyboardInterrupt not raised.")
|
||||
|
||||
def test_prompt_length(self):
|
||||
# Handles simple ASCII prompt
|
||||
ps1 = ">>> "
|
||||
prompt, l = Reader.process_prompt(ps1)
|
||||
self.assertEqual(prompt, ps1)
|
||||
self.assertEqual(l, 4)
|
||||
|
||||
# Handles ANSI escape sequences
|
||||
ps1 = "\033[0;32m>>> \033[0m"
|
||||
prompt, l = Reader.process_prompt(ps1)
|
||||
self.assertEqual(prompt, "\033[0;32m>>> \033[0m")
|
||||
self.assertEqual(l, 4)
|
||||
|
||||
# Handles ANSI escape sequences bracketed in \001 .. \002
|
||||
ps1 = "\001\033[0;32m\002>>> \001\033[0m\002"
|
||||
prompt, l = Reader.process_prompt(ps1)
|
||||
self.assertEqual(prompt, "\033[0;32m>>> \033[0m")
|
||||
self.assertEqual(l, 4)
|
||||
|
||||
# Handles wide characters in prompt
|
||||
ps1 = "樂>> "
|
||||
prompt, l = Reader.process_prompt(ps1)
|
||||
self.assertEqual(prompt, ps1)
|
||||
self.assertEqual(l, 5)
|
||||
|
||||
# Handles wide characters AND ANSI sequences together
|
||||
ps1 = "\001\033[0;32m\002樂>\001\033[0m\002> "
|
||||
prompt, l = Reader.process_prompt(ps1)
|
||||
self.assertEqual(prompt, "\033[0;32m樂>\033[0m> ")
|
||||
self.assertEqual(l, 5)
|
||||
|
||||
def test_completions_updated_on_key_press(self):
|
||||
namespace = {"itertools": itertools}
|
||||
code = "itertools."
|
||||
events = itertools.chain(
|
||||
code_to_events(code),
|
||||
[
|
||||
# Two tabs for completion
|
||||
Event(evt="key", data="\t", raw=bytearray(b"\t")),
|
||||
Event(evt="key", data="\t", raw=bytearray(b"\t")),
|
||||
],
|
||||
code_to_events("a"),
|
||||
)
|
||||
|
||||
completing_reader = functools.partial(
|
||||
prepare_reader,
|
||||
readline_completer=rlcompleter.Completer(namespace).complete,
|
||||
)
|
||||
reader, _ = handle_all_events(events, prepare_reader=completing_reader)
|
||||
|
||||
actual = reader.screen
|
||||
self.assertEqual(len(actual), 2)
|
||||
self.assertEqual(actual[0], f"{code}a")
|
||||
self.assertEqual(actual[1].rstrip(), "itertools.accumulate(")
|
||||
|
||||
def test_key_press_on_tab_press_once(self):
|
||||
namespace = {"itertools": itertools}
|
||||
code = "itertools."
|
||||
events = itertools.chain(
|
||||
code_to_events(code),
|
||||
[
|
||||
Event(evt="key", data="\t", raw=bytearray(b"\t")),
|
||||
],
|
||||
code_to_events("a"),
|
||||
)
|
||||
|
||||
completing_reader = functools.partial(
|
||||
prepare_reader,
|
||||
readline_completer=rlcompleter.Completer(namespace).complete,
|
||||
)
|
||||
reader, _ = handle_all_events(events, prepare_reader=completing_reader)
|
||||
|
||||
self.assert_screen_equal(reader, f"{code}a")
|
||||
|
||||
def test_pos2xy_with_no_columns(self):
|
||||
console = prepare_console([])
|
||||
reader = prepare_reader(console)
|
||||
# Simulate a resize to 0 columns
|
||||
reader.screeninfo = []
|
||||
self.assertEqual(reader.pos2xy(), (0, 0))
|
||||
|
||||
def test_setpos_from_xy_for_non_printing_char(self):
|
||||
code = "# non \u200c printing character"
|
||||
events = code_to_events(code)
|
||||
|
||||
reader, _ = handle_all_events(events)
|
||||
reader.setpos_from_xy(8, 0)
|
||||
self.assertEqual(reader.pos, 7)
|
||||
329
Utils/PythonNew32/Lib/test/test_pyrepl/test_unix_console.py
Normal file
329
Utils/PythonNew32/Lib/test/test_pyrepl/test_unix_console.py
Normal file
@@ -0,0 +1,329 @@
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from functools import partial
|
||||
from test.support import os_helper
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, call, patch, ANY
|
||||
|
||||
from .support import handle_all_events, code_to_events, reader_no_colors
|
||||
|
||||
try:
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.unix_console import UnixConsole
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def unix_console(events, **kwargs):
|
||||
console = UnixConsole()
|
||||
console.get_event = MagicMock(side_effect=events)
|
||||
|
||||
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
|
||||
|
||||
|
||||
handle_events_unix_console = partial(
|
||||
handle_all_events,
|
||||
prepare_console=partial(unix_console),
|
||||
)
|
||||
handle_events_narrow_unix_console = partial(
|
||||
handle_all_events,
|
||||
prepare_console=partial(unix_console, width=5),
|
||||
)
|
||||
handle_events_short_unix_console = partial(
|
||||
handle_all_events,
|
||||
prepare_console=partial(unix_console, height=1),
|
||||
)
|
||||
handle_events_unix_console_height_3 = partial(
|
||||
handle_all_events, prepare_console=partial(unix_console, height=3)
|
||||
)
|
||||
|
||||
|
||||
TERM_CAPABILITIES = {
|
||||
"bel": b"\x07",
|
||||
"civis": b"\x1b[?25l",
|
||||
"clear": b"\x1b[H\x1b[2J",
|
||||
"cnorm": b"\x1b[?12l\x1b[?25h",
|
||||
"cub": b"\x1b[%p1%dD",
|
||||
"cub1": b"\x08",
|
||||
"cud": b"\x1b[%p1%dB",
|
||||
"cud1": b"\n",
|
||||
"cuf": b"\x1b[%p1%dC",
|
||||
"cuf1": b"\x1b[C",
|
||||
"cup": b"\x1b[%i%p1%d;%p2%dH",
|
||||
"cuu": b"\x1b[%p1%dA",
|
||||
"cuu1": b"\x1b[A",
|
||||
"dch1": b"\x1b[P",
|
||||
"dch": b"\x1b[%p1%dP",
|
||||
"el": b"\x1b[K",
|
||||
"hpa": b"\x1b[%i%p1%dG",
|
||||
"ich": b"\x1b[%p1%d@",
|
||||
"ich1": None,
|
||||
"ind": b"\n",
|
||||
"pad": None,
|
||||
"ri": b"\x1bM",
|
||||
"rmkx": b"\x1b[?1l\x1b>",
|
||||
"smkx": b"\x1b[?1h\x1b=",
|
||||
}
|
||||
|
||||
|
||||
@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",
|
||||
lambda s, *args: s + b":" + b",".join(str(i).encode() for i in args),
|
||||
)
|
||||
@patch("_pyrepl.curses.setupterm", lambda a, b: None)
|
||||
@patch(
|
||||
"termios.tcgetattr",
|
||||
lambda _: [
|
||||
27394,
|
||||
3,
|
||||
19200,
|
||||
536872399,
|
||||
38400,
|
||||
38400,
|
||||
[
|
||||
b"\x04",
|
||||
b"\xff",
|
||||
b"\xff",
|
||||
b"\x7f",
|
||||
b"\x17",
|
||||
b"\x15",
|
||||
b"\x12",
|
||||
b"\x00",
|
||||
b"\x03",
|
||||
b"\x1c",
|
||||
b"\x1a",
|
||||
b"\x19",
|
||||
b"\x11",
|
||||
b"\x13",
|
||||
b"\x16",
|
||||
b"\x0f",
|
||||
b"\x01",
|
||||
b"\x00",
|
||||
b"\x14",
|
||||
b"\x00",
|
||||
],
|
||||
],
|
||||
)
|
||||
@patch("termios.tcsetattr", lambda a, b, c: None)
|
||||
@patch("os.write")
|
||||
class TestConsole(TestCase):
|
||||
def test_simple_addition(self, _os_write):
|
||||
code = "12+34"
|
||||
events = code_to_events(code)
|
||||
_, con = handle_events_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, b"1")
|
||||
_os_write.assert_any_call(ANY, b"2")
|
||||
_os_write.assert_any_call(ANY, b"+")
|
||||
_os_write.assert_any_call(ANY, b"3")
|
||||
_os_write.assert_any_call(ANY, b"4")
|
||||
con.restore()
|
||||
|
||||
def test_wrap(self, _os_write):
|
||||
code = "12+34"
|
||||
events = code_to_events(code)
|
||||
_, con = handle_events_narrow_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, b"1")
|
||||
_os_write.assert_any_call(ANY, b"2")
|
||||
_os_write.assert_any_call(ANY, b"+")
|
||||
_os_write.assert_any_call(ANY, b"3")
|
||||
_os_write.assert_any_call(ANY, b"\\")
|
||||
_os_write.assert_any_call(ANY, b"\n")
|
||||
_os_write.assert_any_call(ANY, b"4")
|
||||
con.restore()
|
||||
|
||||
def test_cursor_left(self, _os_write):
|
||||
code = "1"
|
||||
events = itertools.chain(
|
||||
code_to_events(code),
|
||||
[Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))],
|
||||
)
|
||||
_, con = handle_events_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1")
|
||||
con.restore()
|
||||
|
||||
def test_cursor_left_right(self, _os_write):
|
||||
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 = handle_events_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1")
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cuf"] + b":1")
|
||||
con.restore()
|
||||
|
||||
def test_cursor_up(self, _os_write):
|
||||
code = "1\n2+3"
|
||||
events = itertools.chain(
|
||||
code_to_events(code),
|
||||
[Event(evt="key", data="up", raw=bytearray(b"\x1bOA"))],
|
||||
)
|
||||
_, con = handle_events_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cuu"] + b":1")
|
||||
con.restore()
|
||||
|
||||
def test_cursor_up_down(self, _os_write):
|
||||
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 = handle_events_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cuu"] + b":1")
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cud"] + b":1")
|
||||
con.restore()
|
||||
|
||||
def test_cursor_back_write(self, _os_write):
|
||||
events = itertools.chain(
|
||||
code_to_events("1"),
|
||||
[Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))],
|
||||
code_to_events("2"),
|
||||
)
|
||||
_, con = handle_events_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, b"1")
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1")
|
||||
_os_write.assert_any_call(ANY, b"2")
|
||||
con.restore()
|
||||
|
||||
def test_multiline_function_move_up_short_terminal(self, _os_write):
|
||||
# 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 = handle_events_short_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["ri"] + b":")
|
||||
con.restore()
|
||||
|
||||
def test_multiline_function_move_up_down_short_terminal(self, _os_write):
|
||||
# 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 = handle_events_short_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["ri"] + b":")
|
||||
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["ind"] + b":")
|
||||
con.restore()
|
||||
|
||||
def test_resize_bigger_on_multiline_function(self, _os_write):
|
||||
# fmt: off
|
||||
code = (
|
||||
"def f():\n"
|
||||
" foo"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
events = itertools.chain(code_to_events(code))
|
||||
reader, console = handle_events_short_unix_console(
|
||||
events, prepare_reader=reader_no_colors
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
_os_write.assert_has_calls(
|
||||
[
|
||||
call(ANY, TERM_CAPABILITIES["ri"] + b":"),
|
||||
call(ANY, TERM_CAPABILITIES["cup"] + b":0,0"),
|
||||
call(ANY, b"def f():"),
|
||||
]
|
||||
)
|
||||
console.restore()
|
||||
con.restore()
|
||||
|
||||
def test_resize_smaller_on_multiline_function(self, _os_write):
|
||||
# fmt: off
|
||||
code = (
|
||||
"def f():\n"
|
||||
" foo"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
events = itertools.chain(code_to_events(code))
|
||||
reader, console = handle_events_unix_console_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,
|
||||
)
|
||||
_os_write.assert_has_calls(
|
||||
[
|
||||
call(ANY, TERM_CAPABILITIES["ind"] + b":"),
|
||||
call(ANY, TERM_CAPABILITIES["cup"] + b":0,0"),
|
||||
call(ANY, b" foo"),
|
||||
]
|
||||
)
|
||||
console.restore()
|
||||
con.restore()
|
||||
|
||||
def test_getheightwidth_with_invalid_environ(self, _os_write):
|
||||
# gh-128636
|
||||
console = UnixConsole()
|
||||
with os_helper.EnvironmentVarGuard() as env:
|
||||
env["LINES"] = ""
|
||||
self.assertIsInstance(console.getheightwidth(), tuple)
|
||||
env["COLUMNS"] = ""
|
||||
self.assertIsInstance(console.getheightwidth(), tuple)
|
||||
os.environ = []
|
||||
self.assertIsInstance(console.getheightwidth(), tuple)
|
||||
27
Utils/PythonNew32/Lib/test/test_pyrepl/test_utils.py
Normal file
27
Utils/PythonNew32/Lib/test/test_pyrepl/test_utils.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from _pyrepl.utils import str_width, wlen
|
||||
|
||||
|
||||
class TestUtils(TestCase):
|
||||
def test_str_width(self):
|
||||
characters = ['a', '1', '_', '!', '\x1a', '\u263A', '\uffb9']
|
||||
for c in characters:
|
||||
self.assertEqual(str_width(c), 1)
|
||||
|
||||
characters = [chr(99989), chr(99999)]
|
||||
for c in characters:
|
||||
self.assertEqual(str_width(c), 2)
|
||||
|
||||
def test_wlen(self):
|
||||
for c in ['a', 'b', '1', '!', '_']:
|
||||
self.assertEqual(wlen(c), 1)
|
||||
self.assertEqual(wlen('\x1a'), 2)
|
||||
|
||||
char_east_asian_width_N = chr(3800)
|
||||
self.assertEqual(wlen(char_east_asian_width_N), 1)
|
||||
char_east_asian_width_W = chr(4352)
|
||||
self.assertEqual(wlen(char_east_asian_width_W), 2)
|
||||
|
||||
self.assertEqual(wlen('hello'), 5)
|
||||
self.assertEqual(wlen('hello' + '\x1a'), 7)
|
||||
567
Utils/PythonNew32/Lib/test/test_pyrepl/test_windows_console.py
Normal file
567
Utils/PythonNew32/Lib/test/test_pyrepl/test_windows_console.py
Normal file
@@ -0,0 +1,567 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
if sys.platform != "win32":
|
||||
raise unittest.SkipTest("test only relevant on win32")
|
||||
|
||||
|
||||
import itertools
|
||||
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,
|
||||
)
|
||||
import _pyrepl.windows_console as wc
|
||||
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")
|
||||
|
||||
def test_multiline_ctrl_z(self):
|
||||
# see gh-126332
|
||||
code = "abcdefghi"
|
||||
|
||||
events = itertools.chain(
|
||||
code_to_events(code),
|
||||
[
|
||||
Event(evt="key", data='\x1a', raw=bytearray(b'\x1a')),
|
||||
Event(evt="key", data='\x1a', raw=bytearray(b'\x1a')),
|
||||
],
|
||||
)
|
||||
reader, con = self.handle_events_narrow(events)
|
||||
self.assertEqual(reader.cxy, (2, 3))
|
||||
con.restore()
|
||||
|
||||
|
||||
class WindowsConsoleGetEventTests(TestCase):
|
||||
# Virtual-Key Codes: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
VK_BACK = 0x08
|
||||
VK_RETURN = 0x0D
|
||||
VK_LEFT = 0x25
|
||||
VK_7 = 0x37
|
||||
VK_M = 0x4D
|
||||
# Used for miscellaneous characters; it can vary by keyboard.
|
||||
# For the US standard keyboard, the '" key.
|
||||
# For the German keyboard, the Ä key.
|
||||
VK_OEM_7 = 0xDE
|
||||
|
||||
# State of control keys: https://learn.microsoft.com/en-us/windows/console/key-event-record-str
|
||||
RIGHT_ALT_PRESSED = 0x0001
|
||||
RIGHT_CTRL_PRESSED = 0x0004
|
||||
LEFT_ALT_PRESSED = 0x0002
|
||||
LEFT_CTRL_PRESSED = 0x0008
|
||||
ENHANCED_KEY = 0x0100
|
||||
SHIFT_PRESSED = 0x0010
|
||||
|
||||
|
||||
def get_event(self, input_records, **kwargs) -> Console:
|
||||
self.console = WindowsConsole(encoding='utf-8')
|
||||
self.mock = MagicMock(side_effect=input_records)
|
||||
self.console._read_input = self.mock
|
||||
self.console._WindowsConsole__vt_support = kwargs.get("vt_support",
|
||||
False)
|
||||
event = self.console.get_event(block=False)
|
||||
return event
|
||||
|
||||
def get_input_record(self, unicode_char, vcode=0, control=0):
|
||||
return wc.INPUT_RECORD(
|
||||
wc.KEY_EVENT,
|
||||
wc.ConsoleEvent(KeyEvent=
|
||||
wc.KeyEvent(
|
||||
bKeyDown=True,
|
||||
wRepeatCount=1,
|
||||
wVirtualKeyCode=vcode,
|
||||
wVirtualScanCode=0, # not used
|
||||
uChar=wc.Char(unicode_char),
|
||||
dwControlKeyState=control
|
||||
)))
|
||||
|
||||
def test_EmptyBuffer(self):
|
||||
self.assertEqual(self.get_event([None]), None)
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_WINDOW_BUFFER_SIZE_EVENT(self):
|
||||
ir = wc.INPUT_RECORD(
|
||||
wc.WINDOW_BUFFER_SIZE_EVENT,
|
||||
wc.ConsoleEvent(WindowsBufferSizeEvent=
|
||||
wc.WindowsBufferSizeEvent(
|
||||
wc._COORD(0, 0))))
|
||||
self.assertEqual(self.get_event([ir]), Event("resize", ""))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_KEY_EVENT_up_ignored(self):
|
||||
ir = wc.INPUT_RECORD(
|
||||
wc.KEY_EVENT,
|
||||
wc.ConsoleEvent(KeyEvent=
|
||||
wc.KeyEvent(bKeyDown=False)))
|
||||
self.assertEqual(self.get_event([ir]), None)
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_unhandled_events(self):
|
||||
for event in (wc.FOCUS_EVENT, wc.MENU_EVENT, wc.MOUSE_EVENT):
|
||||
ir = wc.INPUT_RECORD(
|
||||
event,
|
||||
# fake data, nothing is read except bKeyDown
|
||||
wc.ConsoleEvent(KeyEvent=
|
||||
wc.KeyEvent(bKeyDown=False)))
|
||||
self.assertEqual(self.get_event([ir]), None)
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_enter(self):
|
||||
ir = self.get_input_record("\r", self.VK_RETURN)
|
||||
self.assertEqual(self.get_event([ir]), Event("key", "\n"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_backspace(self):
|
||||
ir = self.get_input_record("\x08", self.VK_BACK)
|
||||
self.assertEqual(
|
||||
self.get_event([ir]), Event("key", "backspace"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_m(self):
|
||||
ir = self.get_input_record("m", self.VK_M)
|
||||
self.assertEqual(self.get_event([ir]), Event("key", "m"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_M(self):
|
||||
ir = self.get_input_record("M", self.VK_M, self.SHIFT_PRESSED)
|
||||
self.assertEqual(self.get_event([ir]), Event("key", "M"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_left(self):
|
||||
# VK_LEFT is sent as ENHANCED_KEY
|
||||
ir = self.get_input_record("\x00", self.VK_LEFT, self.ENHANCED_KEY)
|
||||
self.assertEqual(self.get_event([ir]), Event("key", "left"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_left_RIGHT_CTRL_PRESSED(self):
|
||||
ir = self.get_input_record(
|
||||
"\x00", self.VK_LEFT, self.RIGHT_CTRL_PRESSED | self.ENHANCED_KEY)
|
||||
self.assertEqual(
|
||||
self.get_event([ir]), Event("key", "ctrl left"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_left_LEFT_CTRL_PRESSED(self):
|
||||
ir = self.get_input_record(
|
||||
"\x00", self.VK_LEFT, self.LEFT_CTRL_PRESSED | self.ENHANCED_KEY)
|
||||
self.assertEqual(
|
||||
self.get_event([ir]), Event("key", "ctrl left"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_left_RIGHT_ALT_PRESSED(self):
|
||||
ir = self.get_input_record(
|
||||
"\x00", self.VK_LEFT, self.RIGHT_ALT_PRESSED | self.ENHANCED_KEY)
|
||||
self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
|
||||
self.assertEqual(
|
||||
self.console.get_event(), Event("key", "left"))
|
||||
# self.mock is not called again, since the second time we read from the
|
||||
# command queue
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_left_LEFT_ALT_PRESSED(self):
|
||||
ir = self.get_input_record(
|
||||
"\x00", self.VK_LEFT, self.LEFT_ALT_PRESSED | self.ENHANCED_KEY)
|
||||
self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
|
||||
self.assertEqual(
|
||||
self.console.get_event(), Event("key", "left"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_m_LEFT_ALT_PRESSED_and_LEFT_CTRL_PRESSED(self):
|
||||
# For the shift keys, Windows does not send anything when
|
||||
# ALT and CTRL are both pressed, so let's test with VK_M.
|
||||
# get_event() receives this input, but does not
|
||||
# generate an event.
|
||||
# This is for e.g. an English keyboard layout, for a
|
||||
# German layout this returns `µ`, see test_AltGr_m.
|
||||
ir = self.get_input_record(
|
||||
"\x00", self.VK_M, self.LEFT_ALT_PRESSED | self.LEFT_CTRL_PRESSED)
|
||||
self.assertEqual(self.get_event([ir]), None)
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_m_LEFT_ALT_PRESSED(self):
|
||||
ir = self.get_input_record(
|
||||
"m", vcode=self.VK_M, control=self.LEFT_ALT_PRESSED)
|
||||
self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
|
||||
self.assertEqual(self.console.get_event(), Event("key", "m"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_m_RIGHT_ALT_PRESSED(self):
|
||||
ir = self.get_input_record(
|
||||
"m", vcode=self.VK_M, control=self.RIGHT_ALT_PRESSED)
|
||||
self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
|
||||
self.assertEqual(self.console.get_event(), Event("key", "m"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_AltGr_7(self):
|
||||
# E.g. on a German keyboard layout, '{' is entered via
|
||||
# AltGr + 7, where AltGr is the right Alt key on the keyboard.
|
||||
# In this case, Windows automatically sets
|
||||
# RIGHT_ALT_PRESSED = 0x0001 + LEFT_CTRL_PRESSED = 0x0008
|
||||
# This can also be entered like
|
||||
# LeftAlt + LeftCtrl + 7 or
|
||||
# LeftAlt + RightCtrl + 7
|
||||
# See https://learn.microsoft.com/en-us/windows/console/key-event-record-str
|
||||
# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscanw
|
||||
ir = self.get_input_record(
|
||||
"{", vcode=self.VK_7,
|
||||
control=self.RIGHT_ALT_PRESSED | self.LEFT_CTRL_PRESSED)
|
||||
self.assertEqual(self.get_event([ir]), Event("key", "{"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_AltGr_m(self):
|
||||
# E.g. on a German keyboard layout, this yields 'µ'
|
||||
# Let's use LEFT_ALT_PRESSED and RIGHT_CTRL_PRESSED this
|
||||
# time, to cover that, too. See above in test_AltGr_7.
|
||||
ir = self.get_input_record(
|
||||
"µ", vcode=self.VK_M, control=self.LEFT_ALT_PRESSED | self.RIGHT_CTRL_PRESSED)
|
||||
self.assertEqual(self.get_event([ir]), Event("key", "µ"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_umlaut_a_german(self):
|
||||
ir = self.get_input_record("ä", self.VK_OEM_7)
|
||||
self.assertEqual(self.get_event([ir]), Event("key", "ä"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
# virtual terminal tests
|
||||
# Note: wVirtualKeyCode, wVirtualScanCode and dwControlKeyState
|
||||
# are always zero in this case.
|
||||
# "\r" and backspace are handled specially, everything else
|
||||
# is handled in "elif self.__vt_support:" in WindowsConsole.get_event().
|
||||
# Hence, only one regular key ("m") and a terminal sequence
|
||||
# are sufficient to test here, the real tests happen in test_eventqueue
|
||||
# and test_keymap.
|
||||
|
||||
def test_enter_vt(self):
|
||||
ir = self.get_input_record("\r")
|
||||
self.assertEqual(self.get_event([ir], vt_support=True),
|
||||
Event("key", "\n"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_backspace_vt(self):
|
||||
ir = self.get_input_record("\x7f")
|
||||
self.assertEqual(self.get_event([ir], vt_support=True),
|
||||
Event("key", "backspace", b"\x7f"))
|
||||
self.assertEqual(self.mock.call_count, 1)
|
||||
|
||||
def test_up_vt(self):
|
||||
irs = [self.get_input_record(x) for x in "\x1b[A"]
|
||||
self.assertEqual(self.get_event(irs, vt_support=True),
|
||||
Event(evt='key', data='up', raw=bytearray(b'\x1b[A')))
|
||||
self.assertEqual(self.mock.call_count, 3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user