| 1 | #!/usr/bin/env python3
|
| 2 | """
|
| 3 | Interactively tests shell bindings.
|
| 4 |
|
| 5 | To invoke this file, run the shell wrapper:
|
| 6 |
|
| 7 | test/stateful.sh bind-quick
|
| 8 | """
|
| 9 | from __future__ import print_function
|
| 10 |
|
| 11 | import sys
|
| 12 | import tempfile
|
| 13 | import time
|
| 14 |
|
| 15 | import harness
|
| 16 | from harness import expect_prompt, register
|
| 17 |
|
| 18 | from test.spec_lib import log
|
| 19 |
|
| 20 |
|
| 21 | def add_foo_fn(sh):
|
| 22 | sh.sendline('function foo() { echo "FOO"; }')
|
| 23 | time.sleep(0.1)
|
| 24 |
|
| 25 |
|
| 26 | def send_bind(sh, opts, keymap=None):
|
| 27 | "Helper method to send a bind command and sleep for a moment. W/ optional keymap."
|
| 28 |
|
| 29 | if keymap:
|
| 30 | sh.sendline(f"bind -m {keymap} {opts}")
|
| 31 | else:
|
| 32 | sh.sendline(f"bind {opts}")
|
| 33 | time.sleep(0.1)
|
| 34 |
|
| 35 |
|
| 36 | @register(not_impl_shells=['dash', 'mksh'])
|
| 37 | def bind_plain(sh):
|
| 38 | "test bind (w/out flags) for adding bindings to readline fns"
|
| 39 | expect_prompt(sh)
|
| 40 |
|
| 41 | # There aren't many readline fns that will work nicely with pexpect (e.g., cursor-based fns)
|
| 42 | # Editing input seems like a reasonable choice
|
| 43 | send_bind(sh, ''' '"\C-x\C-h": backward-delete-char' ''')
|
| 44 | expect_prompt(sh)
|
| 45 |
|
| 46 | sh.send("echo FOOM")
|
| 47 | sh.sendcontrol('x')
|
| 48 | sh.sendcontrol('h')
|
| 49 | sh.sendline("P")
|
| 50 | time.sleep(0.1)
|
| 51 |
|
| 52 | sh.expect("FOOP")
|
| 53 |
|
| 54 |
|
| 55 | @register(not_impl_shells=['dash', 'mksh'])
|
| 56 | def bind_r(sh):
|
| 57 | "test bind -r for removing bindings"
|
| 58 | expect_prompt(sh)
|
| 59 |
|
| 60 | add_foo_fn(sh)
|
| 61 | expect_prompt(sh)
|
| 62 |
|
| 63 | send_bind(sh, """-x '"\C-x\C-f": foo' """)
|
| 64 | expect_prompt(sh)
|
| 65 |
|
| 66 | sh.sendcontrol('x')
|
| 67 | sh.sendcontrol('f')
|
| 68 | time.sleep(0.1)
|
| 69 | sh.expect("FOO")
|
| 70 |
|
| 71 | send_bind(sh, '-r "\C-x\C-f" ')
|
| 72 |
|
| 73 | sh.sendcontrol('x')
|
| 74 | sh.sendcontrol('f')
|
| 75 | time.sleep(0.1)
|
| 76 |
|
| 77 | expect_prompt(sh)
|
| 78 |
|
| 79 |
|
| 80 | @register(not_impl_shells=['dash', 'mksh'])
|
| 81 | def bind_x(sh):
|
| 82 | "test bind -x for setting bindings to custom shell functions"
|
| 83 | expect_prompt(sh)
|
| 84 |
|
| 85 | add_foo_fn(sh)
|
| 86 | expect_prompt(sh)
|
| 87 |
|
| 88 | send_bind(sh, """-x '"\C-x\C-f": foo' """)
|
| 89 | expect_prompt(sh)
|
| 90 |
|
| 91 | sh.sendcontrol('x')
|
| 92 | sh.sendcontrol('f')
|
| 93 | time.sleep(0.1)
|
| 94 |
|
| 95 | sh.expect("FOO")
|
| 96 |
|
| 97 |
|
| 98 | @register(not_impl_shells=['dash', 'mksh'])
|
| 99 | def bind_u(sh):
|
| 100 | "test bind -u for unsetting all bindings to a fn"
|
| 101 | expect_prompt(sh)
|
| 102 |
|
| 103 | send_bind(sh, "'\C-p: yank'")
|
| 104 | expect_prompt(sh)
|
| 105 |
|
| 106 | send_bind(sh, "-u yank")
|
| 107 | expect_prompt(sh)
|
| 108 |
|
| 109 | send_bind(sh, "-q yank")
|
| 110 | sh.expect("yank is not bound to any keys")
|
| 111 |
|
| 112 |
|
| 113 | @register(not_impl_shells=['dash', 'mksh'])
|
| 114 | def bind_q(sh):
|
| 115 | "test bind -q for querying bindings to a fn"
|
| 116 | expect_prompt(sh)
|
| 117 |
|
| 118 | # Probably bound, but we're not testing that precisely
|
| 119 | send_bind(sh, "-q yank")
|
| 120 | sh.expect(["yank can be invoked via", "yank is not bound to any keys"])
|
| 121 |
|
| 122 | expect_prompt(sh)
|
| 123 |
|
| 124 | # Probably NOT bound, but we're not testing that precisely
|
| 125 | send_bind(sh, "-q dump-functions")
|
| 126 | sh.expect([
|
| 127 | "dump-functions can be invoked via",
|
| 128 | "dump-functions is not bound to any keys"
|
| 129 | ])
|
| 130 |
|
| 131 |
|
| 132 | @register(not_impl_shells=['dash', 'mksh'])
|
| 133 | def bind_m(sh):
|
| 134 | "test bind -m for setting bindings in specific keymaps"
|
| 135 | expect_prompt(sh)
|
| 136 |
|
| 137 | send_bind(sh, "-u yank", "vi")
|
| 138 | expect_prompt(sh)
|
| 139 |
|
| 140 | send_bind(sh, "'\C-p: yank'", "emacs")
|
| 141 | expect_prompt(sh)
|
| 142 |
|
| 143 | send_bind(sh, "-q yank", "vi")
|
| 144 | sh.expect("yank is not bound to any keys")
|
| 145 | expect_prompt(sh)
|
| 146 |
|
| 147 | send_bind(sh, "-q yank", "emacs")
|
| 148 | sh.expect("yank can be invoked via")
|
| 149 |
|
| 150 |
|
| 151 | @register(not_impl_shells=['dash', 'mksh'])
|
| 152 | def bind_f(sh):
|
| 153 | "test bind -f for setting bindings from an inputrc init file"
|
| 154 | expect_prompt(sh)
|
| 155 |
|
| 156 | send_bind(sh, "-f spec/testdata/bind/bind_f.inputrc")
|
| 157 | expect_prompt(sh)
|
| 158 |
|
| 159 | send_bind(sh, "-q downcase-word")
|
| 160 | sh.expect('downcase-word can be invoked via.*"\\\C-o\\\C-s\\\C-h"')
|
| 161 |
|
| 162 |
|
| 163 | if __name__ == '__main__':
|
| 164 | try:
|
| 165 | sys.exit(harness.main(sys.argv))
|
| 166 | except RuntimeError as e:
|
| 167 | print('FATAL: %s' % e, file=sys.stderr)
|
| 168 | sys.exit(1)
|