|
7 | 7 | from code import InteractiveConsole
|
8 | 8 | from functools import partial
|
9 | 9 | from unittest import TestCase
|
10 |
| -from unittest.mock import MagicMock, patch |
| 10 | +from unittest.mock import MagicMock, call, patch, ANY |
11 | 11 |
|
12 | 12 | from test.support import requires
|
13 | 13 | from test.support.import_helper import import_module
|
|
22 | 22 | from _pyrepl.console import Console, Event
|
23 | 23 | from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
|
24 | 24 | from _pyrepl.simple_interact import _strip_final_indent
|
| 25 | +from _pyrepl.unix_console import UnixConsole |
25 | 26 | from _pyrepl.unix_eventqueue import EventQueue
|
26 | 27 | from _pyrepl.input import KeymapTranslator
|
27 | 28 | from _pyrepl.keymap import parse_keys, compile_keymap
|
@@ -105,7 +106,8 @@ def handle_all_events(
|
105 | 106 |
|
106 | 107 |
|
107 | 108 | 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), |
109 | 111 | )
|
110 | 112 |
|
111 | 113 |
|
@@ -1198,5 +1200,291 @@ def test_nested_multiple_keymaps(self):
|
1198 | 1200 | self.assertEqual(result, {b"a": {b"b": {b"c": "action"}}})
|
1199 | 1201 |
|
1200 | 1202 |
|
| 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 | + |
1201 | 1489 | if __name__ == "__main__":
|
1202 | 1490 | unittest.main()
|
0 commit comments