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

471 lines, 268 significant
1#!/usr/bin/env python3
2"""
3spec/stateful/job_control.py
4"""
5from __future__ import print_function
6
7import signal
8import sys
9import time
10
11import harness
12from harness import register, expect_prompt
13from test.spec_lib import log
14
15# Hint from Stevens book
16#
17# http://lkml.iu.edu/hypermail/linux/kernel/1006.2/02460.html
18# "TIOCSIG Generate a signal to processes in the
19# current process group of the pty."
20
21# Generated from C header file
22TIOCSIG = 0x40045436
23
24PYCAT = 'python2 -c "import sys; print(sys.stdin.readline().strip() + \'%s\')"'
25
26
27def ctrl_c(sh):
28 sh.sendcontrol('c')
29 #fcntl.ioctl(sh.child_fd, TIOCSIG, signal.SIGINT)
30
31
32def ctrl_z(sh):
33 sh.sendcontrol('z')
34 #fcntl.ioctl(sh.child_fd, TIOCSIG, signal.SIGTSTP)
35
36
37def expect_no_job(sh):
38 """Helper function."""
39 if 'osh' in sh.shell_label:
40 sh.expect('No job to put in the foreground')
41 elif sh.shell_label == 'dash':
42 sh.expect('.*fg: No current job')
43 elif sh.shell_label == 'bash':
44 sh.expect('.*fg: current: no such job.*')
45 else:
46 raise AssertionError()
47
48
49def expect_continued(sh):
50 if 'osh' in sh.shell_label:
51 sh.expect(r'Continue PID \d+')
52 else:
53 sh.expect('cat')
54
55
56@register()
57def bug_1004(sh):
58 'fg twice should not result in fatal error (issue 1004)'
59
60 expect_prompt(sh)
61 sh.sendline('cat')
62
63 time.sleep(0.1)
64
65 debug = False
66 #debug = True
67
68 if debug:
69 import os
70 #os.system('ls -l /proc/%s/fd' % os.getpid())
71
72 # From test/group-session.sh
73 log('harness PID = %d', os.getpid())
74 import subprocess
75 #os.system('ps -o pid,ppid,pgid,sid,tpgid,comm')
76
77 # the child shell is NOT LISTED here because it's associated WITH A
78 # DIFFERENT TERMINAL.
79 subprocess.call(['ps', '-o', 'pid,ppid,pgid,sid,tpgid,comm'])
80
81 ctrl_z(sh)
82
83 sh.expect('.*Stopped.*')
84
85 #sh.expect("\r\n\\[PID \\d+\\] Stopped")
86
87 sh.sendline('') # needed for dash
88 expect_prompt(sh)
89
90 sh.sendline('fg')
91 if 'osh' in sh.shell_label:
92 sh.expect(r'Continue PID \d+')
93 else:
94 sh.expect('cat')
95
96 # Ctrl-C to terminal
97 ctrl_c(sh)
98 expect_prompt(sh)
99
100 sh.sendline('fg')
101
102 expect_no_job(sh)
103
104
105@register()
106def bug_721(sh):
107 'Call fg twice after process exits (issue 721)'
108
109 # This test seems flaky under bash for some reason
110
111 expect_prompt(sh)
112 sh.sendline('cat')
113
114 time.sleep(0.1)
115
116 ctrl_c(sh)
117 expect_prompt(sh)
118
119 sh.sendline('fg')
120 expect_no_job(sh)
121
122 #sh.sendline('')
123 #expect_prompt(sh)
124
125 sh.sendline('fg')
126 expect_no_job(sh)
127
128 sh.sendline('')
129 expect_prompt(sh)
130
131
132@register()
133def bug_1005(sh):
134 'sleep 10 then Ctrl-Z then wait should not hang (issue 1005)'
135
136 expect_prompt(sh)
137
138 sh.sendline('sleep 10')
139
140 time.sleep(0.1)
141 ctrl_z(sh)
142
143 sh.expect(r'.*Stopped.*')
144
145 sh.sendline('wait')
146 sh.sendline('echo status=$?')
147 sh.expect('status=0')
148
149
150@register(skip_shells=['dash'])
151def bug_1005_wait_n(sh):
152 'sleep 10 then Ctrl-Z then wait -n should not hang'
153
154 expect_prompt(sh)
155
156 sh.sendline('sleep 10')
157
158 time.sleep(0.1)
159 ctrl_z(sh)
160
161 sh.expect(r'.*Stopped.*')
162
163 sh.sendline('wait -n')
164 sh.sendline('echo status=$?')
165 sh.expect('status=127')
166
167
168@register()
169def bug_esrch_pipeline_with_builtin(sh):
170 'ESRCH bug - pipeline with builtin'
171
172 # Also see test/bugs.sh, there was a history|less issue
173
174 expect_prompt(sh)
175
176 n = 1
177 for i in range(n):
178 #log('--- Try %d', i)
179
180 if True:
181 #sh.sendline('echo hi | cat')
182 sh.sendline('echo hi | cat | cat | cat')
183 sh.expect(r'.*hi.*')
184 else:
185 sh.sendline('echo hi | tr a-z A-Z')
186 sh.expect(r'.*HI.*')
187
188 time.sleep(0.1)
189
190 sh.sendline('exit')
191
192
193@register()
194def stopped_process(sh):
195 'Resuming a stopped process'
196 expect_prompt(sh)
197
198 sh.sendline('cat')
199
200 time.sleep(0.1) # seems necessary
201
202 ctrl_z(sh)
203
204 sh.expect('.*Stopped.*')
205
206 sh.sendline('') # needed for dash for some reason
207 expect_prompt(sh)
208
209 sh.sendline('fg')
210
211 if 'osh' in sh.shell_label:
212 sh.expect(r'Continue PID \d+')
213 else:
214 sh.expect('cat')
215
216 ctrl_c(sh)
217 expect_prompt(sh)
218
219 sh.sendline('fg')
220 expect_no_job(sh)
221
222
223# OSH doesn't support this because of the lastpipe issue
224# Note: it would be nice to print a message on Ctrl-Z like zsh does:
225# "job can't be suspended"
226
227
228@register(not_impl_shells=['osh', 'osh-cpp'])
229def stopped_pipeline(sh):
230 'Suspend and resume a pipeline (issue 1087)'
231
232 expect_prompt(sh)
233
234 sh.sendline('sleep 10 | cat | cat')
235
236 time.sleep(0.1) # seems necessary
237
238 ctrl_z(sh)
239
240 sh.expect('.*Stopped.*')
241
242 sh.sendline('') # needed for dash for some reason
243 expect_prompt(sh)
244
245 sh.sendline('fg')
246
247 if 'osh' in sh.shell_label:
248 sh.expect(r'Continue PID \d+')
249 else:
250 sh.expect('cat')
251
252 ctrl_c(sh)
253 expect_prompt(sh)
254
255 sh.sendline('fg')
256 expect_no_job(sh)
257
258
259@register()
260def cycle_process_bg_fg(sh):
261 'Suspend and resume a process several times'
262 expect_prompt(sh)
263
264 sh.sendline('cat')
265 time.sleep(0.1) # seems necessary
266
267 for _ in range(3):
268 ctrl_z(sh)
269 sh.expect('.*Stopped.*')
270 sh.sendline('') # needed for dash for some reason
271 expect_prompt(sh)
272 sh.sendline('fg')
273 expect_continued(sh)
274
275 ctrl_c(sh)
276 expect_prompt(sh)
277
278 sh.sendline('fg')
279 expect_no_job(sh)
280
281
282@register()
283def suspend_status(sh):
284 'Ctrl-Z and then look at $?'
285
286 # This test seems flaky under bash for some reason
287
288 expect_prompt(sh)
289 sh.sendline('cat')
290
291 time.sleep(0.1)
292
293 ctrl_z(sh)
294 expect_prompt(sh)
295
296 sh.sendline('echo status=$?')
297 sh.expect('status=148')
298 expect_prompt(sh)
299
300
301@register(skip_shells=['zsh'])
302def no_spurious_tty_take(sh):
303 'A background job getting stopped (e.g. by SIGTTIN) or exiting should not disrupt foreground processes'
304 expect_prompt(sh)
305
306 sh.sendline('cat &') # stop
307 sh.sendline('sleep 0.1 &') # exit
308 expect_prompt(sh)
309
310 # background cat should have been stopped by SIGTTIN immediately, but we don't
311 # hear about it from wait() until the foreground process has been started because
312 # the shell was blocked in readline when the signal fired.
313
314 # TODO: need to wait a bit for jobs to get SIGTTIN. can we be more precise?
315 time.sleep(0.1)
316 sh.sendline(PYCAT % 'bar')
317 if 'osh' in sh.shell_label:
318 # Quirk of osh. TODO: suppress this print for background jobs?
319 sh.expect('.*Stopped.*')
320
321 # foreground process should not have been stopped.
322 sh.sendline('foo')
323 sh.expect('foobar')
324
325 ctrl_c(sh)
326 expect_prompt(sh)
327
328
329@register()
330def fg_current_previous(sh):
331 'Resume the special jobs: %- and %+'
332 expect_prompt(sh)
333
334 # will be terminated as soon as we're done with it
335 sh.sendline('sleep 1000 &')
336
337 # Start two jobs. Both will get stopped by SIGTTIN when they try to read() on
338 # STDIN. According to POSIX, %- and %+ should always refer to stopped jobs if
339 # there are at least two of them.
340 sh.sendline((PYCAT % 'bar') + ' &')
341
342 # TODO: need to wait a bit for jobs to get SIGTTIN. can we be more precise?
343 time.sleep(0.1)
344 sh.sendline('cat &')
345 if 'osh' in sh.shell_label:
346 sh.expect('.*Stopped.*')
347
348 # TODO: need to wait a bit for jobs to get SIGTTIN. can we be more precise?
349 time.sleep(0.1)
350 if 'osh' in sh.shell_label:
351 sh.sendline('')
352 sh.expect('.*Stopped.*')
353
354 # Bring back the newest stopped job
355 sh.sendline('fg %+')
356 if 'osh' in sh.shell_label:
357 sh.expect(r'Continue PID \d+')
358
359 sh.sendline('foo')
360 sh.expect('foo')
361 ctrl_z(sh)
362
363 # Bring back the second-newest stopped job
364 sh.sendline('fg %-')
365 if 'osh' in sh.shell_label:
366 sh.expect(r'Continue PID \d+')
367
368 sh.sendline('')
369 sh.expect('bar')
370
371 # Force cat to exit
372 ctrl_c(sh)
373 expect_prompt(sh)
374 time.sleep(0.1) # wait for cat job to go away
375
376 # Now that cat is gone, %- should refer to the running job
377 sh.sendline('fg %-')
378 if 'osh' in sh.shell_label:
379 sh.expect(r'Continue PID \d+')
380
381 sh.sendline('true')
382 time.sleep(0.5)
383 sh.expect('') # sleep should swallow whatever we write to stdin
384 ctrl_c(sh)
385
386 # %+ and %- should refer to the same thing now that there's only one job
387 sh.sendline('fg %+')
388 if 'osh' in sh.shell_label:
389 sh.expect(r'Continue PID \d+')
390
391 sh.sendline('woof')
392 sh.expect('woof')
393 ctrl_z(sh)
394 sh.sendline('fg %-')
395 if 'osh' in sh.shell_label:
396 sh.expect(r'Continue PID \d+')
397
398 sh.sendline('meow')
399 sh.expect('meow')
400 ctrl_c(sh)
401
402 expect_prompt(sh)
403
404
405@register(skip_shells=['dash'])
406def fg_job_id(sh):
407 'Resume jobs with integral job specs using `fg` builtin'
408 expect_prompt(sh)
409
410 sh.sendline((PYCAT % 'foo') + ' &') # %1
411
412 # TODO: need to wait a bit for jobs to get SIGTTIN. can we be more precise?
413 time.sleep(0.1)
414 sh.sendline((PYCAT % 'bar') + ' &') # %2
415 if 'osh' in sh.shell_label:
416 sh.expect('.*Stopped.*')
417
418 time.sleep(0.1)
419 sh.sendline((PYCAT % 'baz') + ' &') # %3 and %-
420 if 'osh' in sh.shell_label:
421 sh.expect('.*Stopped.*')
422
423 time.sleep(0.1)
424 if 'osh' in sh.shell_label:
425 sh.sendline('')
426 sh.expect('.*Stopped.*')
427
428 sh.sendline('')
429 expect_prompt(sh)
430
431 sh.sendline('fg %1')
432 sh.sendline('')
433 sh.expect('foo')
434
435 sh.sendline('fg %3')
436 sh.sendline('')
437 sh.expect('baz')
438
439 sh.sendline('fg %2')
440 sh.sendline('')
441 sh.expect('bar')
442
443
444@register()
445def wait_job_spec(sh):
446 'Wait using a job spec'
447 expect_prompt(sh)
448
449 sh.sendline('(sleep 2; exit 11) &')
450 sh.sendline('(sleep 1; exit 22) &')
451 sh.sendline('(sleep 3; exit 33) &')
452
453 time.sleep(1)
454 sh.sendline('wait %2; echo status=$?')
455 sh.expect('status=22')
456
457 time.sleep(1)
458 sh.sendline('wait %-; echo status=$?')
459 sh.expect('status=11')
460
461 time.sleep(1)
462 sh.sendline('wait %+; echo status=$?')
463 sh.expect('status=33')
464
465
466if __name__ == '__main__':
467 try:
468 sys.exit(harness.main(sys.argv))
469 except RuntimeError as e:
470 print('FATAL: %s' % e, file=sys.stderr)
471 sys.exit(1)