OILS / spec / stateful / signals.py View on Github | oilshell.org

456 lines, 197 significant
1#!/usr/bin/env python3
2"""
3signals.py
4"""
5from __future__ import print_function
6
7import signal
8import sys
9import time
10
11import harness
12from harness import register, expect_prompt
13
14#
15# Test Cases
16#
17# TODO:
18# - Fold code from demo/
19# - sigwinch-bug.sh -- invokes $OSH with different snippets, then manual window resize
20# - signal-during-read.sh -- bash_read and osh_read with manual kill -HUP $PID
21# trap handler HUP
22# - bug-858-trap.sh -- wait and kill -USR1 $PID
23# trap handler USR1
24# trap handler USR2
25# - Fill out this TEST MATRIX.
26#
27# A. Which shell? osh, bash, dash, etc.
28#
29# B. What mode is it in?
30#
31# 1. Interactive (stdin is a terminal)
32# 2. Non-interactive
33#
34# C. What is the main thread of the shell doing?
35#
36# 1. waiting for external process: sleep 1
37# 2. wait builtin: sleep 5 & wait
38# variants: wait -n: this matters when testing exit code
39# 3. read builtin read
40# variants: FIVE kinds, read -d, read -n, etc.
41# 4. computation, e.g. fibonacci with $(( a + b ))
42#
43# if interactive:
44# 5. keyboard input from terminal with select()
45#
46# Another way to categorize the main loop:
47# 1. running script code
48# 2. running trap code
49# 3. running TAB completion plugin code
50#
51# D. What is it interrupted by?
52#
53# 1. SIGINT
54# 2. SIGTSTP
55# 3. SIGWINCH
56# 4. SIGUSR1 -- doesn't this quit?
57#
58# if interactive:
59# 1. SIGINT Ctrl-C from terminal (relies on signal distribution to child?)
60# 2. SIGTSTP Ctrl-Z from terminal
61#
62# E. What is the signal state?
63#
64# 1. no trap handlers installed
65# 2. trap 'echo X' SIGWINCH
66# 3. trap 'echo X' SIGINT ?
67
68
69@register()
70def sigusr1_trapped_prompt(sh):
71 'Trapped SIGUSR1 while waiting at prompt (bug 1080)'
72
73 # Notes on shell behavior:
74 # bash and dash:
75 # - If there are multiple USR1 signals, only one handler is run.
76 # - The handlers aren't run in between PS1 and PS2. There has to be another
77 # command. So it's run in the InteractiveLoop and not the LineReader.
78 # zsh and mksh:
79 # - It's printed immediately! You don't even have to hit ENTER. That's
80 # probably because the line editor is integrated in both shells.
81 # I think it's OK t match bash and dash.
82
83 expect_prompt(sh)
84
85 sh.sendline("trap 'echo zzz-USR1' USR1")
86 expect_prompt(sh)
87
88 sh.kill(signal.SIGUSR1)
89
90 # send an empty line
91 sh.sendline('')
92 sh.expect('zzz-USR1')
93
94
95@register()
96def sigmultiple_trapped_prompt(sh):
97 'Trapped USR1 and USR2 while waiting at prompt'
98
99 expect_prompt(sh)
100
101 sh.sendline("trap 'echo zzz-USR1' USR1")
102 expect_prompt(sh)
103 sh.sendline("trap 'echo zzz-USR2' USR2")
104 expect_prompt(sh)
105
106 sh.kill(signal.SIGUSR1)
107 time.sleep(
108 0.1) # pause for a bit to avoid racing on signal order in osh-cpp
109 sh.kill(signal.SIGUSR2)
110
111 sh.sendline('echo hi')
112
113 sh.expect('zzz-USR1')
114 sh.expect('zzz-USR2')
115 sh.sendline('hi')
116
117
118@register()
119def sighup_trapped_wait(sh):
120 'trapped SIGHUP during wait builtin'
121
122 sh.sendline("trap 'echo HUP' HUP")
123 expect_prompt(sh)
124 sh.sendline('sleep 1 &')
125 # TODO: expect something like [1] 24370
126 sh.sendline('wait')
127
128 time.sleep(0.1)
129
130 sh.kill(signal.SIGHUP)
131
132 expect_prompt(sh)
133
134 sh.sendline('echo status=$?')
135 sh.expect('status=129')
136
137
138@register()
139def sigint_trapped_wait(sh):
140 'trapped SIGINT during wait builtin'
141
142 # This is different than Ctrl-C during wait builtin, because it's trapped!
143
144 sh.sendline("trap 'echo INT' INT")
145 sh.sendline('sleep 1 &')
146 sh.sendline('wait')
147
148 time.sleep(0.1)
149
150 # simulate window size change
151 sh.kill(signal.SIGINT)
152
153 expect_prompt(sh)
154
155 sh.sendline('echo status=$?')
156 sh.expect('status=130')
157
158
159@register()
160def ctrl_c_after_removing_sigint_trap(sh):
161 'Ctrl-C after removing SIGINT trap (issue 1607)'
162
163 sh.sendline('echo status=$?')
164 sh.expect('status=0')
165
166 sh.sendintr() # SIGINT
167
168 expect_prompt(sh)
169
170 sh.sendline('trap - SIGINT')
171 expect_prompt(sh)
172
173 # Why do we need this? Weird race condition
174 # I would have thought expect_prompt() should be enough synchronization
175 time.sleep(0.1)
176
177 sh.sendintr() # SIGINT
178 expect_prompt(sh)
179
180 # bash, zsh, mksh give status 130, dash and OSH gives 0
181 #sh.sendline('echo status=$?')
182 #sh.expect('status=130')
183
184 sh.sendline('echo hi')
185 sh.expect('hi')
186 expect_prompt(sh)
187
188
189@register()
190def sigwinch_trapped_wait(sh):
191 'trapped SIGWINCH during wait builtin'
192
193 sh.sendline("trap 'echo WINCH' WINCH")
194 sh.sendline('sleep 1 &')
195 sh.sendline('wait')
196
197 time.sleep(0.1)
198
199 # simulate window size change
200 sh.kill(signal.SIGWINCH)
201
202 expect_prompt(sh)
203
204 sh.sendline('echo status=$?')
205 sh.expect('status=156')
206
207
208@register()
209def sigwinch_untrapped_wait(sh):
210 'untrapped SIGWINCH during wait builtin (issue 1067)'
211
212 sh.sendline('sleep 1 &')
213 sh.sendline('wait')
214
215 time.sleep(0.1)
216
217 # simulate window size change
218 sh.kill(signal.SIGWINCH)
219
220 expect_prompt(sh)
221
222 sh.sendline('echo status=$?')
223 sh.expect('status=0')
224
225
226# dash and mksh don't have pipestatus
227@register(skip_shells=['dash', 'mksh'])
228def sigwinch_untrapped_wait_n(sh):
229 'untrapped SIGWINCH during wait -n'
230
231 sh.sendline('sleep 1 &')
232 sh.sendline('wait -n')
233
234 time.sleep(0.1)
235
236 # simulate window size change
237 sh.kill(signal.SIGWINCH)
238
239 expect_prompt(sh)
240
241 sh.sendline('echo status=$?')
242 sh.expect('status=0')
243
244
245# dash and mksh don't have pipestatus
246@register()
247def sigwinch_untrapped_wait_pid(sh):
248 'untrapped SIGWINCH during wait $!'
249
250 sh.sendline('sleep 1 &')
251 sh.sendline('wait $!')
252
253 time.sleep(0.1)
254
255 # simulate window size change
256 sh.kill(signal.SIGWINCH)
257
258 expect_prompt(sh)
259
260 sh.sendline('echo status=$?')
261 sh.expect('status=0')
262
263
264@register()
265def sigwinch_untrapped_external(sh):
266 'untrapped SIGWINCH during external command'
267
268 sh.sendline('sleep 0.5') # slower than timeout
269
270 time.sleep(0.1)
271
272 # simulate window size change
273 sh.kill(signal.SIGWINCH)
274
275 expect_prompt(sh)
276
277 sh.sendline('echo status=$?')
278 sh.expect('status=0')
279
280
281# dash doesn't have pipestatus
282@register(skip_shells=['dash'])
283def sigwinch_untrapped_pipeline(sh):
284 'untrapped SIGWINCH during pipeline'
285
286 sh.sendline('sleep 0.5 | echo x') # slower than timeout
287
288 time.sleep(0.1)
289
290 # simulate window size change
291 sh.kill(signal.SIGWINCH)
292
293 expect_prompt(sh)
294
295 sh.sendline('echo pipestatus=${PIPESTATUS[@]}')
296 sh.expect('pipestatus=0 0')
297
298
299@register()
300def t1(sh):
301 'Ctrl-C during external command'
302
303 sh.sendline('sleep 5')
304
305 time.sleep(0.1)
306 sh.sendintr() # SIGINT
307
308 expect_prompt(sh)
309
310 sh.sendline('echo status=$?')
311 sh.expect('status=130')
312
313
314@register(skip_shells=['mksh'])
315def t4(sh):
316 'Ctrl-C during pipeline'
317 sh.sendline('sleep 5 | cat')
318
319 time.sleep(0.1)
320 sh.sendintr() # SIGINT
321
322 expect_prompt(sh)
323
324 sh.sendline('echo status=$?')
325 sh.expect('status=130')
326
327
328# TODO:
329# - Expand this to call kinds of reads
330# - note that mksh only has some of them
331# - Expand to trapped and untrapped
332# - Expand to Ctrl-C vs. SIGWINCH
333
334
335@register()
336def t2(sh):
337 'Ctrl-C during read builtin'
338
339 sh.sendline('read myvar')
340
341 time.sleep(0.1)
342 sh.sendintr() # SIGINT
343
344 expect_prompt(sh)
345
346 sh.sendline('echo status=$?')
347 sh.expect('status=130')
348
349
350@register()
351def read_d(sh):
352 'Ctrl-C during read -d'
353
354 sh.sendline('read -d :')
355
356 time.sleep(0.1)
357 sh.sendintr() # SIGINT
358
359 expect_prompt(sh)
360
361 sh.sendline('echo status=$?')
362 sh.expect('status=130')
363
364
365@register()
366def c_wait(sh):
367 'Ctrl-C (untrapped) during wait builtin'
368
369 sh.sendline('sleep 5 &')
370 sh.sendline('wait')
371
372 time.sleep(0.1)
373
374 # TODO: actually send Ctrl-C through the terminal, not SIGINT?
375 sh.sendintr() # SIGINT
376
377 expect_prompt(sh)
378
379 sh.sendline('echo status=$?')
380 sh.expect('status=130')
381
382
383@register()
384def c_wait_n(sh):
385 'Ctrl-C (untrapped) during wait -n builtin'
386
387 sh.sendline('sleep 5 &')
388 sh.sendline('wait -n')
389
390 time.sleep(0.1)
391
392 # TODO: actually send Ctrl-C through the terminal, not SIGINT?
393 sh.sendintr() # SIGINT
394
395 expect_prompt(sh)
396
397 sh.sendline('echo status=$?')
398 sh.expect('status=130')
399
400
401@register()
402def c_wait_line(sh):
403 'Ctrl-C (untrapped) cancels entire interactive line'
404
405 # the echo should be cancelled
406 sh.sendline('sleep 10 & wait; echo status=$?')
407
408 time.sleep(0.1)
409
410 # TODO: actually send Ctrl-C through the terminal, not SIGINT?
411 sh.sendintr() # SIGINT
412
413 expect_prompt(sh)
414
415 sh.sendline('echo done=$?')
416 sh.expect('done=130')
417
418
419@register()
420def t5(sh):
421 'Ctrl-C during Command Sub (issue 467)'
422 sh.sendline('`sleep 5`')
423
424 time.sleep(0.1)
425 sh.sendintr() # SIGINT
426
427 expect_prompt(sh)
428
429 sh.sendline('echo status=$?')
430 # TODO: This should be status 130 like bash
431 sh.expect('status=130')
432
433
434@register()
435def loop_break(sh):
436 'Ctrl-C (untrapped) exits loop'
437
438 sh.sendline('while true; do continue; done')
439
440 time.sleep(0.1)
441
442 # TODO: actually send Ctrl-C through the terminal, not SIGINT?
443 sh.sendintr() # SIGINT
444
445 expect_prompt(sh)
446
447 sh.sendline('echo done=$?')
448 sh.expect('done=130')
449
450
451if __name__ == '__main__':
452 try:
453 sys.exit(harness.main(sys.argv))
454 except RuntimeError as e:
455 print('FATAL: %s' % e, file=sys.stderr)
456 sys.exit(1)