Skip to content

Commit fe92193

Browse files
authored
gh-111201: Add tests for unix console class in pyrepl (#118653)
1 parent 9257731 commit fe92193

File tree

1 file changed

+290
-2
lines changed

1 file changed

+290
-2
lines changed

Lib/test/test_pyrepl.py

+290-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from code import InteractiveConsole
88
from functools import partial
99
from unittest import TestCase
10-
from unittest.mock import MagicMock, patch
10+
from unittest.mock import MagicMock, call, patch, ANY
1111

1212
from test.support import requires
1313
from test.support.import_helper import import_module
@@ -22,6 +22,7 @@
2222
from _pyrepl.console import Console, Event
2323
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
2424
from _pyrepl.simple_interact import _strip_final_indent
25+
from _pyrepl.unix_console import UnixConsole
2526
from _pyrepl.unix_eventqueue import EventQueue
2627
from _pyrepl.input import KeymapTranslator
2728
from _pyrepl.keymap import parse_keys, compile_keymap
@@ -105,7 +106,8 @@ def handle_all_events(
105106

106107

107108
handle_events_narrow_console = partial(
108-
handle_all_events, prepare_console=partial(prepare_mock_console, width=10)
109+
handle_all_events,
110+
prepare_console=partial(prepare_mock_console, width=10),
109111
)
110112

111113

@@ -1198,5 +1200,291 @@ def test_nested_multiple_keymaps(self):
11981200
self.assertEqual(result, {b"a": {b"b": {b"c": "action"}}})
11991201

12001202

1203+
def unix_console(events, **kwargs):
1204+
console = UnixConsole()
1205+
console.get_event = MagicMock(side_effect=events)
1206+
1207+
height = kwargs.get("height", 25)
1208+
width = kwargs.get("width", 80)
1209+
console.getheightwidth = MagicMock(side_effect=lambda: (height, width))
1210+
1211+
console.prepare()
1212+
for key, val in kwargs.items():
1213+
setattr(console, key, val)
1214+
return console
1215+
1216+
1217+
handle_events_unix_console = partial(
1218+
handle_all_events,
1219+
prepare_console=partial(unix_console),
1220+
)
1221+
handle_events_narrow_unix_console = partial(
1222+
handle_all_events,
1223+
prepare_console=partial(unix_console, width=5),
1224+
)
1225+
handle_events_short_unix_console = partial(
1226+
handle_all_events,
1227+
prepare_console=partial(unix_console, height=1),
1228+
)
1229+
handle_events_unix_console_height_3 = partial(
1230+
handle_all_events, prepare_console=partial(unix_console, height=3)
1231+
)
1232+
1233+
1234+
TERM_CAPABILITIES = {
1235+
"bel": b"\x07",
1236+
"civis": b"\x1b[?25l",
1237+
"clear": b"\x1b[H\x1b[2J",
1238+
"cnorm": b"\x1b[?12l\x1b[?25h",
1239+
"cub": b"\x1b[%p1%dD",
1240+
"cub1": b"\x08",
1241+
"cud": b"\x1b[%p1%dB",
1242+
"cud1": b"\n",
1243+
"cuf": b"\x1b[%p1%dC",
1244+
"cuf1": b"\x1b[C",
1245+
"cup": b"\x1b[%i%p1%d;%p2%dH",
1246+
"cuu": b"\x1b[%p1%dA",
1247+
"cuu1": b"\x1b[A",
1248+
"dch1": b"\x1b[P",
1249+
"dch": b"\x1b[%p1%dP",
1250+
"el": b"\x1b[K",
1251+
"hpa": b"\x1b[%i%p1%dG",
1252+
"ich": b"\x1b[%p1%d@",
1253+
"ich1": None,
1254+
"ind": b"\n",
1255+
"pad": None,
1256+
"ri": b"\x1bM",
1257+
"rmkx": b"\x1b[?1l\x1b>",
1258+
"smkx": b"\x1b[?1h\x1b=",
1259+
}
1260+
1261+
1262+
@patch("_pyrepl.curses.tigetstr", lambda s: TERM_CAPABILITIES.get(s))
1263+
@patch(
1264+
"_pyrepl.curses.tparm",
1265+
lambda s, *args: s + b":" + b",".join(str(i).encode() for i in args),
1266+
)
1267+
@patch("_pyrepl.curses.setupterm", lambda a, b: None)
1268+
@patch(
1269+
"termios.tcgetattr",
1270+
lambda _: [
1271+
27394,
1272+
3,
1273+
19200,
1274+
536872399,
1275+
38400,
1276+
38400,
1277+
[
1278+
b"\x04",
1279+
b"\xff",
1280+
b"\xff",
1281+
b"\x7f",
1282+
b"\x17",
1283+
b"\x15",
1284+
b"\x12",
1285+
b"\x00",
1286+
b"\x03",
1287+
b"\x1c",
1288+
b"\x1a",
1289+
b"\x19",
1290+
b"\x11",
1291+
b"\x13",
1292+
b"\x16",
1293+
b"\x0f",
1294+
b"\x01",
1295+
b"\x00",
1296+
b"\x14",
1297+
b"\x00",
1298+
],
1299+
],
1300+
)
1301+
@patch("termios.tcsetattr", lambda a, b, c: None)
1302+
@patch("os.write")
1303+
class TestConsole(TestCase):
1304+
def test_simple_addition(self, _os_write):
1305+
code = "12+34"
1306+
events = code_to_events(code)
1307+
_, _ = handle_events_unix_console(events)
1308+
_os_write.assert_any_call(ANY, b"1")
1309+
_os_write.assert_any_call(ANY, b"2")
1310+
_os_write.assert_any_call(ANY, b"+")
1311+
_os_write.assert_any_call(ANY, b"3")
1312+
_os_write.assert_any_call(ANY, b"4")
1313+
1314+
def test_wrap(self, _os_write):
1315+
code = "12+34"
1316+
events = code_to_events(code)
1317+
_, _ = handle_events_narrow_unix_console(events)
1318+
_os_write.assert_any_call(ANY, b"1")
1319+
_os_write.assert_any_call(ANY, b"2")
1320+
_os_write.assert_any_call(ANY, b"+")
1321+
_os_write.assert_any_call(ANY, b"3")
1322+
_os_write.assert_any_call(ANY, b"\\")
1323+
_os_write.assert_any_call(ANY, b"\n")
1324+
_os_write.assert_any_call(ANY, b"4")
1325+
1326+
def test_cursor_left(self, _os_write):
1327+
code = "1"
1328+
events = itertools.chain(
1329+
code_to_events(code),
1330+
[Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))],
1331+
)
1332+
_, _ = handle_events_unix_console(events)
1333+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1")
1334+
1335+
def test_cursor_left_right(self, _os_write):
1336+
code = "1"
1337+
events = itertools.chain(
1338+
code_to_events(code),
1339+
[
1340+
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
1341+
Event(evt="key", data="right", raw=bytearray(b"\x1bOC")),
1342+
],
1343+
)
1344+
_, _ = handle_events_unix_console(events)
1345+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1")
1346+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cuf"] + b":1")
1347+
1348+
def test_cursor_up(self, _os_write):
1349+
code = "1\n2+3"
1350+
events = itertools.chain(
1351+
code_to_events(code),
1352+
[Event(evt="key", data="up", raw=bytearray(b"\x1bOA"))],
1353+
)
1354+
_, _ = handle_events_unix_console(events)
1355+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cuu"] + b":1")
1356+
1357+
def test_cursor_up_down(self, _os_write):
1358+
code = "1\n2+3"
1359+
events = itertools.chain(
1360+
code_to_events(code),
1361+
[
1362+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
1363+
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
1364+
],
1365+
)
1366+
_, _ = handle_events_unix_console(events)
1367+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cuu"] + b":1")
1368+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cud"] + b":1")
1369+
1370+
def test_cursor_back_write(self, _os_write):
1371+
events = itertools.chain(
1372+
code_to_events("1"),
1373+
[Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))],
1374+
code_to_events("2"),
1375+
)
1376+
_, _ = handle_events_unix_console(events)
1377+
_os_write.assert_any_call(ANY, b"1")
1378+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1")
1379+
_os_write.assert_any_call(ANY, b"2")
1380+
1381+
def test_multiline_function_move_up_short_terminal(self, _os_write):
1382+
# fmt: off
1383+
code = (
1384+
"def f():\n"
1385+
" foo"
1386+
)
1387+
# fmt: on
1388+
1389+
events = itertools.chain(
1390+
code_to_events(code),
1391+
[
1392+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
1393+
Event(evt="scroll", data=None),
1394+
],
1395+
)
1396+
_, _ = handle_events_short_unix_console(events)
1397+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["ri"] + b":")
1398+
1399+
def test_multiline_function_move_up_down_short_terminal(self, _os_write):
1400+
# fmt: off
1401+
code = (
1402+
"def f():\n"
1403+
" foo"
1404+
)
1405+
# fmt: on
1406+
1407+
events = itertools.chain(
1408+
code_to_events(code),
1409+
[
1410+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
1411+
Event(evt="scroll", data=None),
1412+
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
1413+
Event(evt="scroll", data=None),
1414+
],
1415+
)
1416+
_, _ = handle_events_short_unix_console(events)
1417+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["ri"] + b":")
1418+
_os_write.assert_any_call(ANY, TERM_CAPABILITIES["ind"] + b":")
1419+
1420+
def test_resize_bigger_on_multiline_function(self, _os_write):
1421+
# fmt: off
1422+
code = (
1423+
"def f():\n"
1424+
" foo"
1425+
)
1426+
# fmt: on
1427+
1428+
events = itertools.chain(code_to_events(code))
1429+
reader, console = handle_events_short_unix_console(events)
1430+
1431+
console.height = 2
1432+
console.getheightwidth = MagicMock(lambda _: (2, 80))
1433+
1434+
def same_reader(_):
1435+
return reader
1436+
1437+
def same_console(events):
1438+
console.get_event = MagicMock(side_effect=events)
1439+
return console
1440+
1441+
_, _ = handle_all_events(
1442+
[Event(evt="resize", data=None)],
1443+
prepare_reader=same_reader,
1444+
prepare_console=same_console,
1445+
)
1446+
_os_write.assert_has_calls(
1447+
[
1448+
call(ANY, TERM_CAPABILITIES["ri"] + b":"),
1449+
call(ANY, TERM_CAPABILITIES["cup"] + b":0,0"),
1450+
call(ANY, b"def f():"),
1451+
]
1452+
)
1453+
1454+
def test_resize_smaller_on_multiline_function(self, _os_write):
1455+
# fmt: off
1456+
code = (
1457+
"def f():\n"
1458+
" foo"
1459+
)
1460+
# fmt: on
1461+
1462+
events = itertools.chain(code_to_events(code))
1463+
reader, console = handle_events_unix_console_height_3(events)
1464+
1465+
console.height = 1
1466+
console.getheightwidth = MagicMock(lambda _: (1, 80))
1467+
1468+
def same_reader(_):
1469+
return reader
1470+
1471+
def same_console(events):
1472+
console.get_event = MagicMock(side_effect=events)
1473+
return console
1474+
1475+
_, _ = handle_all_events(
1476+
[Event(evt="resize", data=None)],
1477+
prepare_reader=same_reader,
1478+
prepare_console=same_console,
1479+
)
1480+
_os_write.assert_has_calls(
1481+
[
1482+
call(ANY, TERM_CAPABILITIES["ind"] + b":"),
1483+
call(ANY, TERM_CAPABILITIES["cup"] + b":0,0"),
1484+
call(ANY, b" foo"),
1485+
]
1486+
)
1487+
1488+
12011489
if __name__ == "__main__":
12021490
unittest.main()

0 commit comments

Comments
 (0)