diff options
Diffstat (limited to 'blessings/tests/test_keyboard.py')
-rw-r--r-- | blessings/tests/test_keyboard.py | 902 |
1 files changed, 902 insertions, 0 deletions
diff --git a/blessings/tests/test_keyboard.py b/blessings/tests/test_keyboard.py new file mode 100644 index 0000000..393b248 --- /dev/null +++ b/blessings/tests/test_keyboard.py @@ -0,0 +1,902 @@ +# -*- coding: utf-8 -*- +"Tests for keyboard support." +# std imports +import functools +import tempfile +try: + from StringIO import StringIO +except ImportError: + import io + StringIO = io.StringIO +import platform +import signal +import curses +import time +import math +import tty # NOQA +import pty +import sys +import os + +# local +from .accessories import ( + read_until_eof, + read_until_semaphore, + SEND_SEMAPHORE, + RECV_SEMAPHORE, + as_subprocess, + TestTerminal, + SEMAPHORE, + all_terms, + echo_off, + xterms, +) + +# 3rd-party +import pytest +import mock + +if sys.version_info[0] == 3: + unichr = chr + + +def test_char_is_ready_interrupted(): + "_char_is_ready() should not be interrupted with a signal handler." + pid, master_fd = pty.fork() + if pid is 0: + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + + # child pauses, writes semaphore and begins awaiting input + global got_sigwinch + got_sigwinch = False + + def on_resize(sig, action): + global got_sigwinch + got_sigwinch = True + + term = TestTerminal() + signal.signal(signal.SIGWINCH, on_resize) + read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(raw=True): + assert term.keystroke(timeout=1.05) == u'' + os.write(sys.__stdout__.fileno(), b'complete') + assert got_sigwinch is True + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, SEND_SEMAPHORE) + read_until_semaphore(master_fd) + stime = time.time() + os.kill(pid, signal.SIGWINCH) + output = read_until_eof(master_fd) + + pid, status = os.waitpid(pid, 0) + assert output == u'complete' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 1.0 + + +def test_char_is_ready_interrupted_nonetype(): + "_char_is_ready() should also allow interruption with timeout of None." + pid, master_fd = pty.fork() + if pid is 0: + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + + # child pauses, writes semaphore and begins awaiting input + global got_sigwinch + got_sigwinch = False + + def on_resize(sig, action): + global got_sigwinch + got_sigwinch = True + + term = TestTerminal() + signal.signal(signal.SIGWINCH, on_resize) + read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(raw=True): + term.keystroke(timeout=1) + os.write(sys.__stdout__.fileno(), b'complete') + assert got_sigwinch is True + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, SEND_SEMAPHORE) + read_until_semaphore(master_fd) + stime = time.time() + time.sleep(0.05) + os.kill(pid, signal.SIGWINCH) + output = read_until_eof(master_fd) + + pid, status = os.waitpid(pid, 0) + assert output == u'complete' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 1.0 + + +def test_char_is_ready_interrupted_interruptable(): + "_char_is_ready() may be interrupted when interruptable=False." + pid, master_fd = pty.fork() + if pid is 0: + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + + # child pauses, writes semaphore and begins awaiting input + global got_sigwinch + got_sigwinch = False + + def on_resize(sig, action): + global got_sigwinch + got_sigwinch = True + + term = TestTerminal() + signal.signal(signal.SIGWINCH, on_resize) + read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(raw=True): + term.keystroke(timeout=1.05, interruptable=False) + os.write(sys.__stdout__.fileno(), b'complete') + assert got_sigwinch is True + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, SEND_SEMAPHORE) + read_until_semaphore(master_fd) + stime = time.time() + time.sleep(0.05) + os.kill(pid, signal.SIGWINCH) + output = read_until_eof(master_fd) + + pid, status = os.waitpid(pid, 0) + assert output == u'complete' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + + +def test_char_is_ready_interrupted_nonetype_interruptable(): + """_char_is_ready() may be interrupted when interruptable=False with + timeout None.""" + pid, master_fd = pty.fork() + if pid is 0: + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + + # child pauses, writes semaphore and begins awaiting input + global got_sigwinch + got_sigwinch = False + + def on_resize(sig, action): + global got_sigwinch + got_sigwinch = True + + term = TestTerminal() + signal.signal(signal.SIGWINCH, on_resize) + read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(raw=True): + term.keystroke(timeout=None, interruptable=False) + os.write(sys.__stdout__.fileno(), b'complete') + assert got_sigwinch is True + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, SEND_SEMAPHORE) + read_until_semaphore(master_fd) + stime = time.time() + time.sleep(0.05) + os.kill(pid, signal.SIGWINCH) + os.write(master_fd, b'X') + output = read_until_eof(master_fd) + + pid, status = os.waitpid(pid, 0) + assert output == u'complete' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + + +def test_keystroke_input_no_kb(): + "keystroke_input() should not call tty.setcbreak() without keyboard." + @as_subprocess + def child(): + with tempfile.NamedTemporaryFile() as stream: + term = TestTerminal(stream=stream) + with mock.patch("tty.setcbreak") as mock_setcbreak: + with term.keystroke_input(): + assert not mock_setcbreak.called + assert term.keyboard_fd is None + child() + + +def test_notty_kb_is_None(): + "keyboard_fd should be None when os.isatty returns False." + # in this scenerio, stream is sys.__stdout__, + # but os.isatty(0) is False, + # such as when piping output to less(1) + @as_subprocess + def child(): + with mock.patch("os.isatty") as mock_isatty: + mock_isatty.return_value = False + term = TestTerminal() + assert term.keyboard_fd is None + child() + + +def test_raw_input_no_kb(): + "keystroke_input(raw=True) should not call tty.setraw() without keyboard." + @as_subprocess + def child(): + with tempfile.NamedTemporaryFile() as stream: + term = TestTerminal(stream=stream) + with mock.patch("tty.setraw") as mock_setraw: + with term.keystroke_input(raw=True): + assert not mock_setraw.called + assert term.keyboard_fd is None + child() + + +def test_char_is_ready_no_kb(): + "_char_is_ready() always immediately returns False without a keyboard." + @as_subprocess + def child(): + term = TestTerminal(stream=StringIO()) + stime = time.time() + assert term.keyboard_fd is None + assert term._char_is_ready(timeout=1.1) is False + assert (math.floor(time.time() - stime) == 1.0) + child() + + +def test_keystroke_0s_keystroke_input_noinput(): + "0-second keystroke without input; '' should be returned." + @as_subprocess + def child(): + term = TestTerminal() + with term.keystroke_input(): + stime = time.time() + inp = term.keystroke(timeout=0) + assert (inp == u'') + assert (math.floor(time.time() - stime) == 0.0) + child() + + +def test_keystroke_0s_keystroke_input_noinput_nokb(): + "0-second keystroke without data in input stream and no keyboard/tty." + @as_subprocess + def child(): + term = TestTerminal(stream=StringIO()) + with term.keystroke_input(): + stime = time.time() + inp = term.keystroke(timeout=0) + assert (inp == u'') + assert (math.floor(time.time() - stime) == 0.0) + child() + + +def test_keystroke_1s_keystroke_input_noinput(): + "1-second keystroke without input; '' should be returned after ~1 second." + @as_subprocess + def child(): + term = TestTerminal() + with term.keystroke_input(): + stime = time.time() + inp = term.keystroke(timeout=1) + assert (inp == u'') + assert (math.floor(time.time() - stime) == 1.0) + child() + + +def test_keystroke_1s_keystroke_input_noinput_nokb(): + "1-second keystroke without input or keyboard." + @as_subprocess + def child(): + term = TestTerminal(stream=StringIO()) + with term.keystroke_input(): + stime = time.time() + inp = term.keystroke(timeout=1) + assert (inp == u'') + assert (math.floor(time.time() - stime) == 1.0) + child() + + +def test_keystroke_0s_keystroke_input_with_input(): + "0-second keystroke with input; Keypress should be immediately returned." + pid, master_fd = pty.fork() + if pid is 0: + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + # child pauses, writes semaphore and begins awaiting input + term = TestTerminal() + read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(): + inp = term.keystroke(timeout=0) + os.write(sys.__stdout__.fileno(), inp.encode('utf-8')) + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, SEND_SEMAPHORE) + os.write(master_fd, u'x'.encode('ascii')) + read_until_semaphore(master_fd) + stime = time.time() + output = read_until_eof(master_fd) + + pid, status = os.waitpid(pid, 0) + assert output == u'x' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + + +def test_keystroke_keystroke_input_with_input_slowly(): + "0-second keystroke with input; Keypress should be immediately returned." + pid, master_fd = pty.fork() + if pid is 0: + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + # child pauses, writes semaphore and begins awaiting input + term = TestTerminal() + read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(): + while True: + inp = term.keystroke(timeout=0.5) + os.write(sys.__stdout__.fileno(), inp.encode('utf-8')) + if inp == 'X': + break + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, SEND_SEMAPHORE) + os.write(master_fd, u'a'.encode('ascii')) + time.sleep(0.1) + os.write(master_fd, u'b'.encode('ascii')) + time.sleep(0.1) + os.write(master_fd, u'cdefgh'.encode('ascii')) + time.sleep(0.1) + os.write(master_fd, u'X'.encode('ascii')) + read_until_semaphore(master_fd) + stime = time.time() + output = read_until_eof(master_fd) + + pid, status = os.waitpid(pid, 0) + assert output == u'abcdefghX' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + + +def test_keystroke_0s_keystroke_input_multibyte_utf8(): + "0-second keystroke with multibyte utf-8 input; should decode immediately." + # utf-8 bytes represent "latin capital letter upsilon". + pid, master_fd = pty.fork() + if pid is 0: # child + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + term = TestTerminal() + read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(): + inp = term.keystroke(timeout=0) + os.write(sys.__stdout__.fileno(), inp.encode('utf-8')) + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, SEND_SEMAPHORE) + os.write(master_fd, u'\u01b1'.encode('utf-8')) + read_until_semaphore(master_fd) + stime = time.time() + output = read_until_eof(master_fd) + pid, status = os.waitpid(pid, 0) + assert output == u'Ʊ' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + + +@pytest.mark.skipif(os.environ.get('TRAVIS', None) is not None or + platform.python_implementation() == 'PyPy', + reason="travis-ci nor pypy handle ^C very well.") +def test_keystroke_0s_raw_input_ctrl_c(): + "0-second keystroke with raw allows receiving ^C." + pid, master_fd = pty.fork() + if pid is 0: # child + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + term = TestTerminal() + read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) + with term.keystroke_input(raw=True): + os.write(sys.__stdout__.fileno(), RECV_SEMAPHORE) + inp = term.keystroke(timeout=0) + os.write(sys.__stdout__.fileno(), inp.encode('latin1')) + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, SEND_SEMAPHORE) + # ensure child is in raw mode before sending ^C, + read_until_semaphore(master_fd) + os.write(master_fd, u'\x03'.encode('latin1')) + stime = time.time() + output = read_until_eof(master_fd) + pid, status = os.waitpid(pid, 0) + assert (output == u'\x03' or + output == u'' and not os.isatty(0)) + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + + +def test_keystroke_0s_keystroke_input_sequence(): + "0-second keystroke with multibyte sequence; should decode immediately." + pid, master_fd = pty.fork() + if pid is 0: # child + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + term = TestTerminal() + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(): + inp = term.keystroke(timeout=0) + os.write(sys.__stdout__.fileno(), inp.name.encode('ascii')) + sys.stdout.flush() + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, u'\x1b[D'.encode('ascii')) + read_until_semaphore(master_fd) + stime = time.time() + output = read_until_eof(master_fd) + pid, status = os.waitpid(pid, 0) + assert output == u'KEY_LEFT' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + + +def test_keystroke_1s_keystroke_input_with_input(): + "1-second keystroke w/multibyte sequence; should return after ~1 second." + pid, master_fd = pty.fork() + if pid is 0: # child + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + term = TestTerminal() + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(): + inp = term.keystroke(timeout=3) + os.write(sys.__stdout__.fileno(), inp.name.encode('utf-8')) + sys.stdout.flush() + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + read_until_semaphore(master_fd) + stime = time.time() + time.sleep(1) + os.write(master_fd, u'\x1b[C'.encode('ascii')) + output = read_until_eof(master_fd) + + pid, status = os.waitpid(pid, 0) + assert output == u'KEY_RIGHT' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 1.0 + + +def test_esc_delay_keystroke_input_035(): + "esc_delay will cause a single ESC (\\x1b) to delay for 0.35." + pid, master_fd = pty.fork() + if pid is 0: # child + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + term = TestTerminal() + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(): + stime = time.time() + inp = term.keystroke(timeout=5) + measured_time = (time.time() - stime) * 100 + os.write(sys.__stdout__.fileno(), ( + '%s %i' % (inp.name, measured_time,)).encode('ascii')) + sys.stdout.flush() + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + read_until_semaphore(master_fd) + stime = time.time() + os.write(master_fd, u'\x1b'.encode('ascii')) + key_name, duration_ms = read_until_eof(master_fd).split() + + pid, status = os.waitpid(pid, 0) + assert key_name == u'KEY_ESCAPE' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + assert 34 <= int(duration_ms) <= 45, duration_ms + + +def test_esc_delay_keystroke_input_135(): + "esc_delay=1.35 will cause a single ESC (\\x1b) to delay for 1.35." + pid, master_fd = pty.fork() + if pid is 0: # child + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + term = TestTerminal() + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(): + stime = time.time() + inp = term.keystroke(timeout=5, esc_delay=1.35) + measured_time = (time.time() - stime) * 100 + os.write(sys.__stdout__.fileno(), ( + '%s %i' % (inp.name, measured_time,)).encode('ascii')) + sys.stdout.flush() + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + read_until_semaphore(master_fd) + stime = time.time() + os.write(master_fd, u'\x1b'.encode('ascii')) + key_name, duration_ms = read_until_eof(master_fd).split() + + pid, status = os.waitpid(pid, 0) + assert key_name == u'KEY_ESCAPE' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 1.0 + assert 134 <= int(duration_ms) <= 145, int(duration_ms) + + +def test_esc_delay_keystroke_input_timout_0(): + """esc_delay still in effect with timeout of 0 ("nonblocking").""" + pid, master_fd = pty.fork() + if pid is 0: # child + try: + cov = __import__('cov_core_init').init() + except ImportError: + cov = None + term = TestTerminal() + os.write(sys.__stdout__.fileno(), SEMAPHORE) + with term.keystroke_input(): + stime = time.time() + inp = term.keystroke(timeout=0) + measured_time = (time.time() - stime) * 100 + os.write(sys.__stdout__.fileno(), ( + '%s %i' % (inp.name, measured_time,)).encode('ascii')) + sys.stdout.flush() + if cov is not None: + cov.stop() + cov.save() + os._exit(0) + + with echo_off(master_fd): + os.write(master_fd, u'\x1b'.encode('ascii')) + read_until_semaphore(master_fd) + stime = time.time() + key_name, duration_ms = read_until_eof(master_fd).split() + + pid, status = os.waitpid(pid, 0) + assert key_name == u'KEY_ESCAPE' + assert os.WEXITSTATUS(status) == 0 + assert math.floor(time.time() - stime) == 0.0 + assert 34 <= int(duration_ms) <= 45, int(duration_ms) + + +def test_keystroke_default_args(): + "Test keyboard.Keystroke constructor with default arguments." + from blessings.keyboard import Keystroke + ks = Keystroke() + assert ks._name is None + assert ks.name == ks._name + assert ks._code is None + assert ks.code == ks._code + assert u'x' == u'x' + ks + assert ks.is_sequence is False + assert repr(ks) in ("u''", # py26, 27 + "''",) # py33 + + +def test_a_keystroke(): + "Test keyboard.Keystroke constructor with set arguments." + from blessings.keyboard import Keystroke + ks = Keystroke(ucs=u'x', code=1, name=u'the X') + assert ks._name is u'the X' + assert ks.name == ks._name + assert ks._code is 1 + assert ks.code == ks._code + assert u'xx' == u'x' + ks + assert ks.is_sequence is True + assert repr(ks) == "the X" + + +def test_get_keyboard_codes(): + "Test all values returned by get_keyboard_codes are from curses." + from blessings.keyboard import ( + get_keyboard_codes, + CURSES_KEYCODE_OVERRIDE_MIXIN, + ) + exemptions = dict(CURSES_KEYCODE_OVERRIDE_MIXIN) + for value, keycode in get_keyboard_codes().items(): + if keycode in exemptions: + assert value == exemptions[keycode] + continue + assert hasattr(curses, keycode) + assert getattr(curses, keycode) == value + + +def test_alternative_left_right(): + "Test _alternative_left_right behavior for space/backspace." + from blessings.keyboard import _alternative_left_right + term = mock.Mock() + term._cuf1 = u'' + term._cub1 = u'' + assert not bool(_alternative_left_right(term)) + term._cuf1 = u' ' + term._cub1 = u'\b' + assert not bool(_alternative_left_right(term)) + term._cuf1 = u'seq-right' + term._cub1 = u'seq-left' + assert (_alternative_left_right(term) == { + u'seq-right': curses.KEY_RIGHT, + u'seq-left': curses.KEY_LEFT}) + + +def test_cuf1_and_cub1_as_RIGHT_LEFT(all_terms): + "Test that cuf1 and cub1 are assigned KEY_RIGHT and KEY_LEFT." + from blessings.keyboard import get_keyboard_sequences + + @as_subprocess + def child(kind): + term = TestTerminal(kind=kind, force_styling=True) + keymap = get_keyboard_sequences(term) + if term._cuf1: + assert term._cuf1 in keymap + assert keymap[term._cuf1] == term.KEY_RIGHT + if term._cub1: + assert term._cub1 in keymap + if term._cub1 == '\b': + assert keymap[term._cub1] == term.KEY_BACKSPACE + else: + assert keymap[term._cub1] == term.KEY_LEFT + + child(all_terms) + + +def test_get_keyboard_sequences_sort_order(xterms): + "ordereddict ensures sequences are ordered longest-first." + @as_subprocess + def child(): + term = TestTerminal(force_styling=True) + maxlen = None + for sequence, code in term._keymap.items(): + if maxlen is not None: + assert len(sequence) <= maxlen + assert sequence + maxlen = len(sequence) + child() + + +def test_get_keyboard_sequence(monkeypatch): + "Test keyboard.get_keyboard_sequence. " + import curses.has_key + import blessings.keyboard + + (KEY_SMALL, KEY_LARGE, KEY_MIXIN) = range(3) + (CAP_SMALL, CAP_LARGE) = 'cap-small cap-large'.split() + (SEQ_SMALL, SEQ_LARGE, SEQ_MIXIN, SEQ_ALT_CUF1, SEQ_ALT_CUB1) = ( + b'seq-small-a', + b'seq-large-abcdefg', + b'seq-mixin', + b'seq-alt-cuf1', + b'seq-alt-cub1_') + + # patch curses functions + monkeypatch.setattr(curses, 'tigetstr', + lambda cap: {CAP_SMALL: SEQ_SMALL, + CAP_LARGE: SEQ_LARGE}[cap]) + + monkeypatch.setattr(curses.has_key, '_capability_names', + dict(((KEY_SMALL, CAP_SMALL,), + (KEY_LARGE, CAP_LARGE,)))) + + # patch global sequence mix-in + monkeypatch.setattr(blessings.keyboard, + 'DEFAULT_SEQUENCE_MIXIN', ( + (SEQ_MIXIN.decode('latin1'), KEY_MIXIN),)) + + # patch for _alternative_left_right + term = mock.Mock() + term._cuf1 = SEQ_ALT_CUF1.decode('latin1') + term._cub1 = SEQ_ALT_CUB1.decode('latin1') + keymap = blessings.keyboard.get_keyboard_sequences(term) + + assert list(keymap.items()) == [ + (SEQ_LARGE.decode('latin1'), KEY_LARGE), + (SEQ_ALT_CUB1.decode('latin1'), curses.KEY_LEFT), + (SEQ_ALT_CUF1.decode('latin1'), curses.KEY_RIGHT), + (SEQ_SMALL.decode('latin1'), KEY_SMALL), + (SEQ_MIXIN.decode('latin1'), KEY_MIXIN)] + + +def test_resolve_sequence(): + "Test resolve_sequence for order-dependent mapping." + from blessings.keyboard import resolve_sequence, OrderedDict + mapper = OrderedDict(((u'SEQ1', 1), + (u'SEQ2', 2), + # takes precedence over LONGSEQ, first-match + (u'KEY_LONGSEQ_longest', 3), + (u'LONGSEQ', 4), + # wont match, LONGSEQ is first-match in this order + (u'LONGSEQ_longer', 5), + # falls through for L{anything_else} + (u'L', 6))) + codes = {1: u'KEY_SEQ1', + 2: u'KEY_SEQ2', + 3: u'KEY_LONGSEQ_longest', + 4: u'KEY_LONGSEQ', + 5: u'KEY_LONGSEQ_longer', + 6: u'KEY_L'} + ks = resolve_sequence(u'', mapper, codes) + assert ks == u'' + assert ks.name is None + assert ks.code is None + assert ks.is_sequence is False + assert repr(ks) in ("u''", # py26, 27 + "''",) # py33 + + ks = resolve_sequence(u'notfound', mapper=mapper, codes=codes) + assert ks == u'n' + assert ks.name is None + assert ks.code is None + assert ks.is_sequence is False + assert repr(ks) in (u"u'n'", "'n'",) + + ks = resolve_sequence(u'SEQ1', mapper, codes) + assert ks == u'SEQ1' + assert ks.name == u'KEY_SEQ1' + assert ks.code is 1 + assert ks.is_sequence is True + assert repr(ks) in (u"KEY_SEQ1", "KEY_SEQ1") + + ks = resolve_sequence(u'LONGSEQ_longer', mapper, codes) + assert ks == u'LONGSEQ' + assert ks.name == u'KEY_LONGSEQ' + assert ks.code is 4 + assert ks.is_sequence is True + assert repr(ks) in (u"KEY_LONGSEQ", "KEY_LONGSEQ") + + ks = resolve_sequence(u'LONGSEQ', mapper, codes) + assert ks == u'LONGSEQ' + assert ks.name == u'KEY_LONGSEQ' + assert ks.code is 4 + assert ks.is_sequence is True + assert repr(ks) in (u"KEY_LONGSEQ", "KEY_LONGSEQ") + + ks = resolve_sequence(u'Lxxxxx', mapper, codes) + assert ks == u'L' + assert ks.name == u'KEY_L' + assert ks.code is 6 + assert ks.is_sequence is True + assert repr(ks) in (u"KEY_L", "KEY_L") + + +def test_keypad_mixins_and_aliases(): + """ Test PC-Style function key translations when in ``keypad`` mode.""" + # Key plain app modified + # Up ^[[A ^[OA ^[[1;mA + # Down ^[[B ^[OB ^[[1;mB + # Right ^[[C ^[OC ^[[1;mC + # Left ^[[D ^[OD ^[[1;mD + # End ^[[F ^[OF ^[[1;mF + # Home ^[[H ^[OH ^[[1;mH + @as_subprocess + def child(kind): + term = TestTerminal(kind=kind, force_styling=True) + from blessings.keyboard import resolve_sequence + + resolve = functools.partial(resolve_sequence, + mapper=term._keymap, + codes=term._keycodes) + + assert resolve(unichr(10)).name == "KEY_ENTER" + assert resolve(unichr(13)).name == "KEY_ENTER" + assert resolve(unichr(8)).name == "KEY_BACKSPACE" + assert resolve(unichr(9)).name == "KEY_TAB" + assert resolve(unichr(27)).name == "KEY_ESCAPE" + assert resolve(unichr(127)).name == "KEY_DELETE" + assert resolve(u"\x1b[A").name == "KEY_UP" + assert resolve(u"\x1b[B").name == "KEY_DOWN" + assert resolve(u"\x1b[C").name == "KEY_RIGHT" + assert resolve(u"\x1b[D").name == "KEY_LEFT" + assert resolve(u"\x1b[U").name == "KEY_PGDOWN" + assert resolve(u"\x1b[V").name == "KEY_PGUP" + assert resolve(u"\x1b[H").name == "KEY_HOME" + assert resolve(u"\x1b[F").name == "KEY_END" + assert resolve(u"\x1b[K").name == "KEY_END" + assert resolve(u"\x1bOM").name == "KEY_ENTER" + assert resolve(u"\x1bOj").name == "KEY_KP_MULTIPLY" + assert resolve(u"\x1bOk").name == "KEY_KP_ADD" + assert resolve(u"\x1bOl").name == "KEY_KP_SEPARATOR" + assert resolve(u"\x1bOm").name == "KEY_KP_SUBTRACT" + assert resolve(u"\x1bOn").name == "KEY_KP_DECIMAL" + assert resolve(u"\x1bOo").name == "KEY_KP_DIVIDE" + assert resolve(u"\x1bOX").name == "KEY_KP_EQUAL" + assert resolve(u"\x1bOp").name == "KEY_KP_0" + assert resolve(u"\x1bOq").name == "KEY_KP_1" + assert resolve(u"\x1bOr").name == "KEY_KP_2" + assert resolve(u"\x1bOs").name == "KEY_KP_3" + assert resolve(u"\x1bOt").name == "KEY_KP_4" + assert resolve(u"\x1bOu").name == "KEY_KP_5" + assert resolve(u"\x1bOv").name == "KEY_KP_6" + assert resolve(u"\x1bOw").name == "KEY_KP_7" + assert resolve(u"\x1bOx").name == "KEY_KP_8" + assert resolve(u"\x1bOy").name == "KEY_KP_9" + assert resolve(u"\x1b[1~").name == "KEY_FIND" + assert resolve(u"\x1b[2~").name == "KEY_INSERT" + assert resolve(u"\x1b[3~").name == "KEY_DELETE" + assert resolve(u"\x1b[4~").name == "KEY_SELECT" + assert resolve(u"\x1b[5~").name == "KEY_PGUP" + assert resolve(u"\x1b[6~").name == "KEY_PGDOWN" + assert resolve(u"\x1b[7~").name == "KEY_HOME" + assert resolve(u"\x1b[8~").name == "KEY_END" + assert resolve(u"\x1b[OA").name == "KEY_UP" + assert resolve(u"\x1b[OB").name == "KEY_DOWN" + assert resolve(u"\x1b[OC").name == "KEY_RIGHT" + assert resolve(u"\x1b[OD").name == "KEY_LEFT" + assert resolve(u"\x1b[OF").name == "KEY_END" + assert resolve(u"\x1b[OH").name == "KEY_HOME" + assert resolve(u"\x1bOP").name == "KEY_F1" + assert resolve(u"\x1bOQ").name == "KEY_F2" + assert resolve(u"\x1bOR").name == "KEY_F3" + assert resolve(u"\x1bOS").name == "KEY_F4" + + child('xterm') |