Skip to content

Commit

Permalink
[interactive] Handle Ctrl-C during command sub.
Browse files Browse the repository at this point in the history
Add cases to test/interactive.py to verify it

TODO: test that exit codes match bash and other shells.  It looks like
they use exit code 130 consistently.

Addresses issue #467.
  • Loading branch information
Andy C committed Jan 21, 2022
1 parent 3d9fb2c commit d958daf
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 42 deletions.
8 changes: 7 additions & 1 deletion core/main_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,13 @@ def Interactive(flag, cmd_ev, c_parser, display, prompt_plugin, errfmt):
ui.PrintAst(node, flag)
break

is_return, _ = cmd_ev.ExecuteAndCatch(node)
try:
is_return, _ = cmd_ev.ExecuteAndCatch(node)
except KeyboardInterrupt: # issue 467, Ctrl-C during $(sleep 1)
is_return = False
display.EraseLines()
status = 130 # 128 + 2
break

status = cmd_ev.LastStatus()
if is_return:
Expand Down
143 changes: 102 additions & 41 deletions test/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
"""
from __future__ import print_function

import os
import pexpect
import signal
import sys
import os
import time


SHELL = os.environ.get("OSH_TEST_INTERACTIVE_SHELL", "bin/osh")
Expand Down Expand Up @@ -117,56 +118,116 @@ def __exit__(self, t, v, tb):
pass


### Test Cases Below ##########################################################
def main(argv):
with InteractiveTest('Ctrl-C during external command') as osh:
osh.sendline('sleep 5')

time.sleep(0.1)
osh.sendintr() # SIGINT

osh.expect(r'.*\$') # expect prompt

osh.sendline('echo status=$?')
osh.expect('status=130')

with InteractiveTest('Ctrl-C during read builtin') as osh:
osh.sendline('read')

time.sleep(0.1)
osh.sendintr() # SIGINT

osh.expect(r'.*\$') # expect prompt

osh.sendline('echo status=$?')
osh.expect('status=130')

with InteractiveTest('Ctrl-C during wait builtin') as osh:
osh.sendline('sleep 5 &')
osh.sendline('wait')

time.sleep(0.1)
osh.sendintr() # SIGINT

osh.expect(r'.*\$') # expect prompt

osh.sendline('echo status=$?')
# TODO: Should be exit code 130 like bash
osh.expect('status=0')

with InteractiveTest('Ctrl-C during pipeline') as osh:
osh.sendline('sleep 5 | cat')

time.sleep(0.1)
osh.sendintr() # SIGINT

osh.expect(r'.*\$') # expect prompt

osh.sendline('echo status=$?')
osh.expect('status=130')

with InteractiveTest("Ctrl-C during Command Sub (issue 467)") as osh:
osh.sendline('`sleep 5`')

time.sleep(0.1)
osh.sendintr() # SIGINT

osh.expect(r'.*\$') # expect prompt

with InteractiveTest("Regression test for issue #1004") as osh:
osh.expect(r'.*\$ ')
osh.sendline("cat")
stop_process__hack("cat")
osh.expect("\r\n\\[PID \\d+\\] Stopped")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect(r"Continue PID \d+")
osh.sendline('echo status=$?')
# TODO: This should be status 130 like bash
osh.expect('status=0')

#osh.sendcontrol("c")
osh.sendintr() # SIGINT
with InteractiveTest("fg twice should not result in fatal error (issue 1004)") as osh:
osh.expect(r'.*\$ ')
osh.sendline("cat")
stop_process__hack("cat")
osh.expect("\r\n\\[PID \\d+\\] Stopped")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect(r"Continue PID \d+")

osh.expect(r".*\$")
osh.sendline("fg")
osh.expect("No job to put in the foreground")
#osh.sendcontrol("c")
osh.sendintr() # SIGINT

osh.expect(r".*\$")
osh.sendline("fg")
osh.expect("No job to put in the foreground")

with InteractiveTest("Test resuming a killed process") as osh:
osh.expect(r'.*\$ ')
osh.sendline("cat")
stop_process__hack("cat")
osh.expect("\r\n\\[PID \\d+\\] Stopped")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect(r"Continue PID \d+")
kill_process("cat")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect("No job to put in the foreground")
with InteractiveTest('Test resuming a killed process') as osh:
osh.expect(r'.*\$ ')
osh.sendline("cat")
stop_process__hack("cat")
osh.expect("\r\n\\[PID \\d+\\] Stopped")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect(r"Continue PID \d+")
kill_process("cat")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect("No job to put in the foreground")


with InteractiveTest("Regression test for issue #721") as osh:
osh.expect(r".*\$")
osh.sendline("cat")
with InteractiveTest('Call fg after process exits (issue 721)') as osh:
osh.expect(r".*\$")
osh.sendline("cat")

#osh.sendcontrol("c")
osh.sendintr() # SIGINT
#osh.sendcontrol("c")
osh.sendintr() # SIGINT

osh.expect(r".*\$")
osh.sendline("fg")
osh.expect("No job to put in the foreground")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect("No job to put in the foreground")
osh.expect(r".*\$")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect("No job to put in the foreground")
osh.expect(r".*\$")
osh.sendline("fg")
osh.expect("No job to put in the foreground")
osh.expect(r".*\$")

return 1 if g_failures else 0

### Don't touch this ##########################################################

exit(1 if g_failures else 0)
if __name__ == '__main__':
try:
sys.exit(main(sys.argv))
except RuntimeError as e:
print('FATAL: %s' % e, file=sys.stderr)
sys.exit(1)

0 comments on commit d958daf

Please sign in to comment.