OILS / spec / stateful / bind.py View on Github | oils.pub

236 lines, 137 significant
1#!/usr/bin/env python3
2"""
3Interactively tests shell bindings.
4
5To invoke this file, run the shell wrapper:
6
7 test/stateful.sh bind-quick
8"""
9from __future__ import print_function
10
11from itertools import count
12import sys
13import tempfile
14import time
15
16import harness
17from harness import expect_prompt, register
18
19from test.spec_lib import log
20
21
22def add_foo_fn(sh):
23 sh.sendline('function foo() { echo "FOO"; }')
24 time.sleep(0.1)
25
26
27def send_bind(sh, opts, keymap=None):
28 "Helper method to send a bind command and sleep for a moment. W/ optional keymap."
29
30 if keymap:
31 sh.sendline(f"bind -m {keymap} {opts}")
32 else:
33 sh.sendline(f"bind {opts}")
34 time.sleep(0.1)
35
36
37@register(not_impl_shells=['dash', 'mksh'])
38def bind_plain(sh):
39 "test bind (w/out flags) for adding bindings to readline fns"
40 expect_prompt(sh)
41
42 # There aren't many readline fns that will work nicely with pexpect (e.g., cursor-based fns)
43 # Editing input seems like a reasonable choice
44 send_bind(sh, ''' '"\C-x\C-h": backward-delete-char' ''')
45 expect_prompt(sh)
46
47 sh.send("echo FOOM")
48 sh.sendcontrol('x')
49 sh.sendcontrol('h')
50 sh.sendline("P")
51 time.sleep(0.1)
52
53 sh.expect("FOOP")
54
55
56@register(not_impl_shells=['dash', 'mksh'])
57def bind_r_for_bind_x_osh_fn(sh):
58 """
59 test bind -r for removing bindings to arbitrary cmds made with bind -x
60 (regular readline fn bind removal is tested in noninteractive builtin-bind.test.sh)
61 """
62 expect_prompt(sh)
63
64 add_foo_fn(sh)
65 expect_prompt(sh)
66
67 send_bind(sh, """-x '"\C-x\C-f": foo' """)
68 expect_prompt(sh)
69
70 sh.sendcontrol('x')
71 sh.sendcontrol('f')
72 time.sleep(0.1)
73 sh.expect("FOO")
74
75 send_bind(sh, '-r "\C-x\C-f" ')
76
77 sh.sendcontrol('x')
78 sh.sendcontrol('f')
79 time.sleep(0.1)
80
81 expect_prompt(sh)
82
83
84@register(not_impl_shells=['dash', 'mksh'])
85def bind_x(sh):
86 "test bind -x for setting bindings to custom shell functions"
87 expect_prompt(sh)
88
89 add_foo_fn(sh)
90 expect_prompt(sh)
91
92 send_bind(sh, """-x '"\C-x\C-f": foo' """)
93 expect_prompt(sh)
94
95 sh.sendcontrol('x')
96 sh.sendcontrol('f')
97 time.sleep(0.1)
98
99 sh.expect("FOO")
100
101
102@register(not_impl_shells=['dash', 'mksh'])
103def bind_x_runtime_envvar_vals(sh):
104 "test bind -x for using env var runtime values (e.g., 'echo $PWD' should change with dir)"
105 expect_prompt(sh)
106
107 sh.sendline("export BIND_X_VAR=foo")
108
109 send_bind(sh, """-x '"\C-x\C-f": echo $BIND_X_VAR' """)
110 expect_prompt(sh)
111
112 sh.sendline("export BIND_X_VAR=bar")
113 expect_prompt(sh)
114
115 sh.sendcontrol('x')
116 sh.sendcontrol('f')
117 time.sleep(0.1)
118
119 sh.expect("bar")
120
121
122@register(not_impl_shells=['dash', 'mksh'])
123def bind_x_readline_line(sh):
124 "test bind -x for correctly setting $READLINE_LINE for the cmd"
125 expect_prompt(sh)
126
127 send_bind(sh, """-x '"\C-x\C-f": echo Current line is: $READLINE_LINE' """)
128 expect_prompt(sh)
129
130 sh.send('abcdefghijklmnopqrstuvwxyz')
131
132 sh.sendcontrol('x')
133 sh.sendcontrol('f')
134 time.sleep(0.1)
135
136 # must not match any other output (like debug output or shell names)
137 sh.expect("Current line is: abcdefghijklmnopqrstuvwxyz")
138
139 sh.sendline('[[ -v READLINE_LINE ]] && echo "READLINE_LINE is set" || echo "READLINE_LINE is unset"')
140 sh.expect("READLINE_LINE is unset")
141
142
143@register(not_impl_shells=['dash', 'mksh'])
144def bind_x_readline_point(sh):
145 "test bind -x for correctly setting $READLINE_POINT for the cmd"
146 cmd_str = 'abcdefghijklmnop'
147 expected_rl_point = len(cmd_str)
148
149 expect_prompt(sh)
150
151 send_bind(sh, """-x '"\C-x\C-f": echo Cursor point at: $READLINE_POINT' """)
152 expect_prompt(sh)
153
154 sh.send(cmd_str)
155
156 sh.sendcontrol('x')
157 sh.sendcontrol('f')
158 time.sleep(0.1)
159
160 sh.expect("Cursor point at: " + str(expected_rl_point))
161
162 sh.sendline('[[ -v READLINE_POINT ]] && echo "READLINE_POINT is set" || echo "READLINE_POINT is unset"')
163 sh.expect("READLINE_POINT is unset")
164
165
166@register(not_impl_shells=['dash', 'mksh'])
167def bind_u(sh):
168 "test bind -u for unsetting all bindings to a fn"
169 expect_prompt(sh)
170
171 send_bind(sh, "'\C-p: yank'")
172 expect_prompt(sh)
173
174 send_bind(sh, "-u yank")
175 expect_prompt(sh)
176
177 send_bind(sh, "-q yank")
178 sh.expect("yank is not bound to any keys")
179
180
181@register(not_impl_shells=['dash', 'mksh'])
182def bind_q(sh):
183 "test bind -q for querying bindings to a fn"
184 expect_prompt(sh)
185
186 # Probably bound, but we're not testing that precisely
187 send_bind(sh, "-q yank")
188 sh.expect(["yank can be invoked via", "yank is not bound to any keys"])
189
190 expect_prompt(sh)
191
192 # Probably NOT bound, but we're not testing that precisely
193 send_bind(sh, "-q dump-functions")
194 sh.expect([
195 "dump-functions can be invoked via",
196 "dump-functions is not bound to any keys"
197 ])
198
199
200@register(not_impl_shells=['dash', 'mksh'])
201def bind_m(sh):
202 "test bind -m for setting bindings in specific keymaps"
203 expect_prompt(sh)
204
205 send_bind(sh, "-u yank", "vi")
206 expect_prompt(sh)
207
208 send_bind(sh, "'\C-p: yank'", "emacs")
209 expect_prompt(sh)
210
211 send_bind(sh, "-q yank", "vi")
212 sh.expect("yank is not bound to any keys")
213 expect_prompt(sh)
214
215 send_bind(sh, "-q yank", "emacs")
216 sh.expect("yank can be invoked via")
217
218
219@register(not_impl_shells=['dash', 'mksh'])
220def bind_f(sh):
221 "test bind -f for setting bindings from an inputrc init file"
222 expect_prompt(sh)
223
224 send_bind(sh, "-f spec/testdata/bind/bind_f.inputrc")
225 expect_prompt(sh)
226
227 send_bind(sh, "-q downcase-word")
228 sh.expect('downcase-word can be invoked via.*"\\\C-o\\\C-s\\\C-h"')
229
230
231if __name__ == '__main__':
232 try:
233 sys.exit(harness.main(sys.argv))
234 except RuntimeError as e:
235 print('FATAL: %s' % e, file=sys.stderr)
236 sys.exit(1)