| 1 | #!/usr/bin/env python3
|
| 2 | """
|
| 3 | signals.py
|
| 4 | """
|
| 5 | from __future__ import print_function
|
| 6 |
|
| 7 | import signal
|
| 8 | import sys
|
| 9 | import time
|
| 10 |
|
| 11 | import harness
|
| 12 | from 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()
|
| 70 | def 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()
|
| 96 | def 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()
|
| 119 | def 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()
|
| 139 | def 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()
|
| 160 | def 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()
|
| 190 | def 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()
|
| 209 | def 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'])
|
| 228 | def 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()
|
| 247 | def 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()
|
| 265 | def 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'])
|
| 283 | def 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()
|
| 300 | def 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'])
|
| 315 | def 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()
|
| 336 | def 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()
|
| 351 | def 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()
|
| 366 | def 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()
|
| 384 | def 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()
|
| 402 | def 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()
|
| 420 | def 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()
|
| 435 | def 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 |
|
| 451 | if __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)
|