Skip to content

Commit 69e8779

Browse files
committed
add WindowsConsoleGetEventTests
1 parent ed035e5 commit 69e8779

File tree

1 file changed

+219
-0
lines changed

1 file changed

+219
-0
lines changed

Lib/test/test_pyrepl/test_windows_console.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
MOVE_DOWN,
2424
ERASE_IN_LINE,
2525
)
26+
import _pyrepl.windows_console as wc
2627
except ImportError:
2728
pass
2829

@@ -346,5 +347,223 @@ def test_multiline_ctrl_z(self):
346347
con.restore()
347348

348349

350+
class WindowsConsoleGetEventTests(TestCase):
351+
# Virtual-Key Codes: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
352+
VK_BACK = 0x08
353+
VK_RETURN = 0x0D
354+
VK_LEFT = 0x25
355+
VK_7 = 0x37
356+
VK_M = 0x4D
357+
# Used for miscellaneous characters; it can vary by keyboard.
358+
# For the US standard keyboard, the '" key.
359+
# For the German keyboard, the Ä key.
360+
VK_OEM_7 = 0xDE
361+
362+
# State of control keys: https://learn.microsoft.com/en-us/windows/console/key-event-record-str
363+
RIGHT_ALT_PRESSED = 0x0001
364+
RIGHT_CTRL_PRESSED = 0x0004
365+
LEFT_ALT_PRESSED = 0x0002
366+
LEFT_CTRL_PRESSED = 0x0008
367+
ENHANCED_KEY = 0x0100
368+
SHIFT_PRESSED = 0x0010
369+
370+
371+
def get_event(self, input_records, **kwargs) -> Console:
372+
self.console = WindowsConsole(encoding='utf-8')
373+
self.mock = MagicMock(side_effect=input_records)
374+
self.console._read_input = self.mock
375+
self.console._WindowsConsole__vt_support = kwargs.get("vt_support",
376+
False)
377+
event = self.console.get_event(block=False)
378+
return event
379+
380+
def get_input_record(self, unicode_char, vcode=0, control=0):
381+
return wc.INPUT_RECORD(
382+
wc.KEY_EVENT,
383+
wc.ConsoleEvent(KeyEvent=
384+
wc.KeyEvent(
385+
bKeyDown=True,
386+
wRepeatCount=1,
387+
wVirtualKeyCode=vcode,
388+
wVirtualScanCode=0, # not used
389+
uChar=wc.Char(unicode_char),
390+
dwControlKeyState=control
391+
)))
392+
393+
def test_EmptyBuffer(self):
394+
# GetNumberOfConsoleInputEvents populates lpcNumberOfEvents with 0
395+
self.assertEqual(self.get_event([None]), None)
396+
self.assertEqual(self.mock.call_count, 1)
397+
398+
def test_WINDOW_BUFFER_SIZE_EVENT(self):
399+
ir = wc.INPUT_RECORD(
400+
wc.WINDOW_BUFFER_SIZE_EVENT,
401+
wc.ConsoleEvent(WindowsBufferSizeEvent=
402+
wc.WindowsBufferSizeEvent(
403+
wc._COORD(0, 0))))
404+
self.assertEqual(self.get_event([ir]), Event("resize", ""))
405+
self.assertEqual(self.mock.call_count, 1)
406+
407+
def test_KEY_EVENT_up_ignored(self):
408+
ir = wc.INPUT_RECORD(
409+
wc.KEY_EVENT,
410+
wc.ConsoleEvent(KeyEvent=
411+
wc.KeyEvent(bKeyDown=False)))
412+
self.assertEqual(self.get_event([ir]), None)
413+
self.assertEqual(self.mock.call_count, 1)
414+
415+
def test_unhandled_events(self):
416+
for event in (wc.FOCUS_EVENT, wc.MENU_EVENT, wc.MOUSE_EVENT):
417+
ir = wc.INPUT_RECORD(
418+
event,
419+
# fake data, nothing is read except bKeyDown
420+
wc.ConsoleEvent(KeyEvent=
421+
wc.KeyEvent(bKeyDown=False)))
422+
self.assertEqual(self.get_event([ir]), None)
423+
self.assertEqual(self.mock.call_count, 1)
424+
425+
def test_enter(self):
426+
ir = self.get_input_record("\r", self.VK_RETURN)
427+
self.assertEqual(self.get_event([ir]), Event("key", "\n", b"\n"))
428+
self.assertEqual(self.mock.call_count, 1)
429+
430+
def test_backspace(self):
431+
ir = self.get_input_record("\x08", self.VK_BACK)
432+
self.assertEqual(
433+
self.get_event([ir]), Event("key", "backspace", "\x08"))
434+
self.assertEqual(self.mock.call_count, 1)
435+
436+
def test_m(self):
437+
ir = self.get_input_record("m", self.VK_M)
438+
self.assertEqual(self.get_event([ir]), Event("key", "m", "m"))
439+
self.assertEqual(self.mock.call_count, 1)
440+
441+
def test_M(self):
442+
ir = self.get_input_record("M", self.VK_M, self.SHIFT_PRESSED)
443+
self.assertEqual(self.get_event([ir]), Event("key", "M", "M"))
444+
self.assertEqual(self.mock.call_count, 1)
445+
446+
def test_left(self):
447+
# VK_LEFT is sent as ENHANCED_KEY
448+
ir = self.get_input_record("\x00", self.VK_LEFT, self.ENHANCED_KEY)
449+
self.assertEqual(self.get_event([ir]), Event("key", "left", "\x00"))
450+
self.assertEqual(self.mock.call_count, 1)
451+
452+
def test_left_RIGHT_CTRL_PRESSED(self):
453+
ir = self.get_input_record(
454+
"\x00", self.VK_LEFT, self.RIGHT_CTRL_PRESSED | self.ENHANCED_KEY)
455+
self.assertEqual(
456+
self.get_event([ir]), Event("key", "ctrl left", "\x00"))
457+
self.assertEqual(self.mock.call_count, 1)
458+
459+
def test_left_LEFT_CTRL_PRESSED(self):
460+
ir = self.get_input_record(
461+
"\x00", self.VK_LEFT, self.LEFT_CTRL_PRESSED | self.ENHANCED_KEY)
462+
self.assertEqual(
463+
self.get_event([ir]), Event("key", "ctrl left", "\x00"))
464+
self.assertEqual(self.mock.call_count, 1)
465+
466+
def test_left_RIGHT_ALT_PRESSED(self):
467+
ir = self.get_input_record(
468+
"\x00", self.VK_LEFT, self.RIGHT_ALT_PRESSED | self.ENHANCED_KEY)
469+
self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
470+
self.assertEqual(
471+
self.console.get_event(), Event("key", "left", "\x00"))
472+
# self.mock is not called again, since the second time we read from the
473+
# command queue
474+
self.assertEqual(self.mock.call_count, 1)
475+
476+
def test_left_LEFT_ALT_PRESSED(self):
477+
ir = self.get_input_record(
478+
"\x00", self.VK_LEFT, self.LEFT_ALT_PRESSED | self.ENHANCED_KEY)
479+
self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
480+
self.assertEqual(
481+
self.console.get_event(), Event("key", "left", "\x00"))
482+
self.assertEqual(self.mock.call_count, 1)
483+
484+
def test_m_LEFT_ALT_PRESSED_and_LEFT_CTRL_PRESSED(self):
485+
# For the shift keys, Windows does not send anything when
486+
# ALT and CTRL are both pressed, so let's test with VK_M.
487+
# get_event() receives this input, but does not
488+
# generate an event.
489+
# This is for e.g. an English keyboard layout, for a
490+
# German layout this returns `µ`, see test_AltGr_m.
491+
ir = self.get_input_record(
492+
"\x00", self.VK_M, self.LEFT_ALT_PRESSED | self.LEFT_CTRL_PRESSED)
493+
self.assertEqual(self.get_event([ir]), None)
494+
self.assertEqual(self.mock.call_count, 1)
495+
496+
def test_m_LEFT_ALT_PRESSED(self):
497+
ir = self.get_input_record(
498+
"m", vcode=self.VK_M, control=self.LEFT_ALT_PRESSED)
499+
self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
500+
self.assertEqual(self.console.get_event(), Event("key", "m", "m"))
501+
self.assertEqual(self.mock.call_count, 1)
502+
503+
def test_m_RIGHT_ALT_PRESSED(self):
504+
ir = self.get_input_record(
505+
"m", vcode=self.VK_M, control=self.RIGHT_ALT_PRESSED)
506+
self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
507+
self.assertEqual(self.console.get_event(), Event("key", "m", "m"))
508+
self.assertEqual(self.mock.call_count, 1)
509+
510+
def test_AltGr_7(self):
511+
# E.g. on a German keyboard layout, '{' is entered via
512+
# AltGr + 7, where AltGr is the right Alt key on the keyboard.
513+
# In this case, Windows automatically sets
514+
# RIGHT_ALT_PRESSED = 0x0001 + LEFT_CTRL_PRESSED = 0x0008
515+
# This can also be entered like
516+
# LeftAlt + LeftCtrl + 7 or
517+
# LeftAlt + RightCtrl + 7
518+
# See https://learn.microsoft.com/en-us/windows/console/key-event-record-str
519+
# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscanw
520+
ir = self.get_input_record(
521+
"{", vcode=self.VK_7,
522+
control=self.RIGHT_ALT_PRESSED | self.LEFT_CTRL_PRESSED)
523+
self.assertEqual(self.get_event([ir]), Event("key", "{", "{"))
524+
self.assertEqual(self.mock.call_count, 1)
525+
526+
def test_AltGr_m(self):
527+
# E.g. on a German keyboard layout, this yields 'µ'
528+
# Let's use LEFT_ALT_PRESSED and RIGHT_CTRL_PRESSED this
529+
# time, to cover that, too. See above in test_AltGr_7.
530+
ir = self.get_input_record(
531+
"µ", vcode=self.VK_M, control=self.LEFT_ALT_PRESSED | self.RIGHT_CTRL_PRESSED)
532+
self.assertEqual(self.get_event([ir]), Event("key", "µ", "µ"))
533+
self.assertEqual(self.mock.call_count, 1)
534+
535+
def test_umlaut_a_german(self):
536+
ir = self.get_input_record("ä", self.VK_OEM_7)
537+
self.assertEqual(self.get_event([ir]), Event("key", "ä", "ä"))
538+
self.assertEqual(self.mock.call_count, 1)
539+
540+
# virtual terminal tests
541+
# Note: wVirtualKeyCode, wVirtualScanCode and dwControlKeyState
542+
# are always zero in this case.
543+
# "\r" and backspace are handled specially, everything else
544+
# is handled in "elif self.__vt_support:" in WindowsConsole.get_event().
545+
# Hence, only one regular key ("m") and a terminal sequence
546+
# are sufficient to test here, the real tests happen in test_eventqueue
547+
# and test_keymap.
548+
549+
def test_enter_vt(self):
550+
ir = self.get_input_record("\r")
551+
self.assertEqual(self.get_event([ir], vt_support=True),
552+
Event("key", "\n", b"\n"))
553+
self.assertEqual(self.mock.call_count, 1)
554+
555+
def test_backspace_vt(self):
556+
ir = self.get_input_record("\x7f")
557+
self.assertEqual(self.get_event([ir], vt_support=True),
558+
Event("key", "backspace", b"\x7f"))
559+
self.assertEqual(self.mock.call_count, 1)
560+
561+
def test_up_vt(self):
562+
irs = [self.get_input_record(x) for x in "\x1b[A"]
563+
self.assertEqual(self.get_event(irs, vt_support=True),
564+
Event(evt='key', data='up', raw=bytearray(b'\x1b[A')))
565+
self.assertEqual(self.mock.call_count, 3)
566+
567+
349568
if __name__ == "__main__":
350569
unittest.main()

0 commit comments

Comments
 (0)