Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-119306: Break up _pyrepl tests #119307

Merged
merged 9 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,490 changes: 0 additions & 1,490 deletions Lib/test/test_pyrepl.py

This file was deleted.

14 changes: 14 additions & 0 deletions Lib/test/test_pyrepl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import os
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")
readline = import_module("readline")


def load_tests(*args):
return load_package_tests(os.path.dirname(__file__), *args)
4 changes: 4 additions & 0 deletions Lib/test/test_pyrepl/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import unittest
from test.test_pyrepl import load_tests

unittest.main()
141 changes: 141 additions & 0 deletions Lib/test/test_pyrepl/support.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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


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 prepare_reader(console: Console, **kwargs):
config = ReadlineConfig(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):
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
return reader, console


handle_events_narrow_console = partial(
handle_all_events,
prepare_console=partial(prepare_console, width=10),
)


class FakeConsole(Console):
def __init__(self, events, encoding="utf-8"):
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) -> None:
pass

def repaint(self) -> None:
pass
102 changes: 102 additions & 0 deletions Lib/test/test_pyrepl/test_input.py
Original file line number Diff line number Diff line change
@@ -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())
74 changes: 74 additions & 0 deletions Lib/test/test_pyrepl/test_keymap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import unittest

from _pyrepl.keymap import parse_keys, compile_keymap


class TestParseKeys(unittest.TestCase):
def test_single_character(self):
self.assertEqual(parse_keys("a"), ["a"])
self.assertEqual(parse_keys("b"), ["b"])
self.assertEqual(parse_keys("1"), ["1"])

def test_escape_sequences(self):
self.assertEqual(parse_keys("\\n"), ["\n"])
self.assertEqual(parse_keys("\\t"), ["\t"])
self.assertEqual(parse_keys("\\\\"), ["\\"])
self.assertEqual(parse_keys("\\'"), ["'"])
self.assertEqual(parse_keys('\\"'), ['"'])

def test_control_sequences(self):
self.assertEqual(parse_keys("\\C-a"), ["\x01"])
self.assertEqual(parse_keys("\\C-b"), ["\x02"])
self.assertEqual(parse_keys("\\C-c"), ["\x03"])

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_keynames(self):
self.assertEqual(parse_keys("\\<up>"), ["up"])
self.assertEqual(parse_keys("\\<down>"), ["down"])
self.assertEqual(parse_keys("\\<left>"), ["left"])
self.assertEqual(parse_keys("\\<right>"), ["right"])

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"])


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"}}})
Loading
Loading