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

474 lines, 277 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 time.sleep(
314 0.1
315 ) # TODO: need to wait a bit for jobs to get SIGTTIN. can we be more precise?
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 sh.sendline(
335 'sleep 1000 &') # will be terminated as soon as we're done with it
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 time.sleep(
343 0.1
344 ) # TODO: need to wait a bit for jobs to get SIGTTIN. can we be more precise?
345 sh.sendline('cat &')
346 if 'osh' in sh.shell_label:
347 sh.expect('.*Stopped.*')
348
349 time.sleep(
350 0.1
351 ) # TODO: need to wait a bit for jobs to get SIGTTIN. can we be more precise?
352 if 'osh' in sh.shell_label:
353 sh.sendline('')
354 sh.expect('.*Stopped.*')
355
356 # Bring back the newest stopped job
357 sh.sendline('fg %+')
358 if 'osh' in sh.shell_label:
359 sh.expect(r'Continue PID \d+')
360
361 sh.sendline('foo')
362 sh.expect('foo')
363 ctrl_z(sh)
364
365 # Bring back the second-newest stopped job
366 sh.sendline('fg %-')
367 if 'osh' in sh.shell_label:
368 sh.expect(r'Continue PID \d+')
369
370 sh.sendline('')
371 sh.expect('bar')
372
373 # Force cat to exit
374 ctrl_c(sh)
375 expect_prompt(sh)
376 time.sleep(0.1) # wait for cat job to go away
377
378 # Now that cat is gone, %- should refer to the running job
379 sh.sendline('fg %-')
380 if 'osh' in sh.shell_label:
381 sh.expect(r'Continue PID \d+')
382
383 sh.sendline('true')
384 time.sleep(0.5)
385 sh.expect('') # sleep should swallow whatever we write to stdin
386 ctrl_c(sh)
387
388 # %+ and %- should refer to the same thing now that there's only one job
389 sh.sendline('fg %+')
390 if 'osh' in sh.shell_label:
391 sh.expect(r'Continue PID \d+')
392
393 sh.sendline('woof')
394 sh.expect('woof')
395 ctrl_z(sh)
396 sh.sendline('fg %-')
397 if 'osh' in sh.shell_label:
398 sh.expect(r'Continue PID \d+')
399
400 sh.sendline('meow')
401 sh.expect('meow')
402 ctrl_c(sh)
403
404 expect_prompt(sh)
405
406
407@register(skip_shells=['dash'])
408def fg_job_id(sh):
409 'Resume jobs with integral job specs using `fg` builtin'
410 expect_prompt(sh)
411
412 sh.sendline((PYCAT % 'foo') + ' &') # %1
413
414 time.sleep(
415 0.1
416 ) # TODO: need to wait a bit for jobs to get SIGTTIN. can we be more precise?
417 sh.sendline((PYCAT % 'bar') + ' &') # %2
418 if 'osh' in sh.shell_label:
419 sh.expect('.*Stopped.*')
420
421 time.sleep(0.1)
422 sh.sendline((PYCAT % 'baz') + ' &') # %3 and %-
423 if 'osh' in sh.shell_label:
424 sh.expect('.*Stopped.*')
425
426 time.sleep(0.1)
427 if 'osh' in sh.shell_label:
428 sh.sendline('')
429 sh.expect('.*Stopped.*')
430
431 sh.sendline('')
432 expect_prompt(sh)
433
434 sh.sendline('fg %1')
435 sh.sendline('')
436 sh.expect('foo')
437
438 sh.sendline('fg %3')
439 sh.sendline('')
440 sh.expect('baz')
441
442 sh.sendline('fg %2')
443 sh.sendline('')
444 sh.expect('bar')
445
446
447@register()
448def wait_job_spec(sh):
449 'Wait using a job spec'
450 expect_prompt(sh)
451
452 sh.sendline('(sleep 2; exit 11) &')
453 sh.sendline('(sleep 1; exit 22) &')
454 sh.sendline('(sleep 3; exit 33) &')
455
456 time.sleep(1)
457 sh.sendline('wait %2; echo status=$?')
458 sh.expect('status=22')
459
460 time.sleep(1)
461 sh.sendline('wait %-; echo status=$?')
462 sh.expect('status=11')
463
464 time.sleep(1)
465 sh.sendline('wait %+; echo status=$?')
466 sh.expect('status=33')
467
468
469if __name__ == '__main__':
470 try:
471 sys.exit(harness.main(sys.argv))
472 except RuntimeError as e:
473 print('FATAL: %s' % e, file=sys.stderr)
474 sys.exit(1)