1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
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
|