Permalink
Browse files

Spec tests and a skeleton for the trap builtin.

Also added demo/trap.sh, and moved some other scripts in gold/ to demo/.
(These tests are difficult to automate.)

- Print signal names with -l
- Parse arguments and handle errors

Still need to hook up the code to signals and hooks.

Addresses issue #60.
  • Loading branch information...
Andy Chu
Andy Chu committed Jan 20, 2018
1 parent 7ffbfde commit 2cc3933d9a8b12e3bbddac05e727f5289d455416
View
@@ -980,19 +980,97 @@ def DeclareTypeset(argv, mem, funcs):
return status
def Trap(argv, traps):
# TODO: register trap
import signal
def _MakeSignals():
"""Piggy-back on CPython to get a list of portable signals.
When Oil is ported to C, we might want to do something like bash/dash.
"""
names = {}
for name in dir(signal):
# don't want SIG_DFL or SIG_IGN
if name.startswith('SIG') and not name.startswith('SIG_'):
int_val = getattr(signal, name)
names[name] = int_val
return names
_SIGNAL_NAMES = _MakeSignals()
_HOOK_NAMES = ('EXIT', 'ERR', 'RETURN', 'DEBUG')
TRAP_SPEC = _Register('trap')
TRAP_SPEC.ShortFlag('-p')
TRAP_SPEC.ShortFlag('-l')
def Trap(argv, traps, ex):
arg, i = TRAP_SPEC.Parse(argv)
status = 0
if arg.p: # Print registered handlers
for name, value in traps.iteritems():
print(name)
print(value)
print()
sys.stdout.flush()
return 0
if arg.l: # List valid signals and hooks
ordered = _SIGNAL_NAMES.items()
ordered.sort(key=lambda x: x[1])
for name in _HOOK_NAMES:
print(' %s' % name)
for name, int_val in ordered:
print('%02d %s' % (int_val, name))
sys.stdout.flush()
return 0
try:
code_str = argv[0]
sig_spec = argv[1]
except IndexError:
util.usage('trap CODE SIGNAL_SPEC')
return 1
# NOTE: sig_spec isn't validated when removing handlers.
if code_str == '-':
try:
del traps[sig_spec]
except KeyError:
pass
return 0
# Try parsing code first.
node = ex.ParseTrapCode(code_str)
if node is None:
return 1 # ParseTrapCode() prints an error for us.
# Register a hook
if sig_spec in _HOOK_NAMES:
traps[sig_spec] = node
return 0
# Register a signal
sig_val = _SIGNAL_NAMES.get(sig_spec)
if sig_val is not None:
traps[sig_spec] = node
# TODO: call signal.signal()
return 0
util.error('Invalid signal %r' % sig_spec)
return 1
# Example:
# trap -- 'echo "hi there" | wc ' SIGINT
#
# Then hit Ctrl-C.
#
# Yeah you need the EvalHelper. traps is a list of signals to parsed
# NODES.
util.warn('*** trap not implemented ***')
return 0
def Umask(argv):
View
@@ -198,13 +198,34 @@ def _EvalHelper(self, c_parser, source_name):
self.arena.PopSource()
def _Eval(self, argv):
# NOTE: in oil, eval shouldn't take multiple args. For clarity, 'eval ls
# foo' will be an "extra arg" error.
# TODO: set -o sane-eval should change eval to
code_str = ' '.join(argv)
line_reader = reader.StringLineReader(code_str, self.arena)
_, c_parser = parse_lib.MakeParser(line_reader, self.arena)
return self._EvalHelper(c_parser, '<eval string>')
def ParseTrapCode(self, code_str):
"""
Returns:
A node, or None if the code is invalid.
"""
line_reader = reader.StringLineReader(code_str, self.arena)
_, c_parser = parse_lib.MakeParser(line_reader, self.arena)
source_name = '<trap string>'
self.arena.PushSource(source_name)
try:
node = c_parser.ParseWholeFile()
if not node:
util.error('Parse error in %r:', source_name)
err = c_parser.Error()
ui.PrintErrorStack(err, self.arena, sys.stderr)
return None
finally:
self.arena.PopSource()
return node
def _Source(self, argv):
try:
path = argv[0]
@@ -302,7 +323,7 @@ def _RunBuiltin(self, builtin_id, argv):
status = self._Source(argv)
elif builtin_id == EBuiltin.TRAP:
status = builtin.Trap(argv, self.traps)
status = builtin.Trap(argv, self.traps, self)
elif builtin_id == EBuiltin.UMASK:
status = builtin.Umask(argv)
View
@@ -86,6 +86,10 @@ def Disable(self):
(None, 'strict-control-flow'),
(None, 'strict-errexit'),
(None, 'strict-array'),
# TODO: Add strict-arg-parse? For example, 'trap 1 2 3' shouldn't be
# valid, because it has an extra argument. Builtins are inconsistent about
# checking this.
]
_SET_OPTION_NAMES = set(name for _, name in SET_OPTIONS)
View
2 gold/fd-lib.sh → demo/fd-lib.sh 100755 → 100644
@@ -1,5 +1,5 @@
#!/bin/bash
. gold/open-fds.sh
. demo/open-fds.sh
count_func
View
@@ -0,0 +1,4 @@
#!/bin/bash
. demo/fd-lib.sh
File renamed without changes.
View
@@ -0,0 +1,59 @@
#!/bin/bash
#
# Demo of traps.
sigterm() {
echo "sigterm [$@] $?"
# quit the process -- otherwise we resume!
exit
}
child() {
trap 'sigterm x y' SIGTERM
echo child
for i in $(seq 5); do
sleep 1
done
}
readonly SH=bash
#readonly SH=dash # bad trap
#readonly SH=mksh
#readonly SH=zsh
child2() {
$SH -c '
sigterm() {
echo "sigterm [$@] $?"
# quit the process -- otherwise we resume!
exit
}
trap "sigterm x y" SIGTERM
trap -p
echo child
for i in $(seq 5); do
sleep 1
done
' &
}
start-and-kill() {
$0 child &
#child2
echo "started $!"
sleep 0.1 # a little race to allow things to be printed
# NOTE: The process only dies after one second. The "sleep 1" is run until
# completion, and then the signal handler is run, which calls "exit".
echo "killing $!"
kill -s SIGTERM $!
wait $!
echo status=$?
}
"$@"
File renamed without changes.
View

This file was deleted.

Oops, something went wrong.
View
@@ -0,0 +1,122 @@
#!/bin/bash
### trap -l
trap -l | grep SIGINT >/dev/null
## status: 0
## N-I dash/mksh status: 1
### trap -p
trap -p | grep trap >/dev/null
## status: 0
## N-I dash/mksh status: 1
### Register invalid trap
trap 'foo' SIGINVALID
## status: 1
### Invalid trap invocation
trap 'foo'
echo status=$?
## stdout: status=1
## OK bash stdout: status=2
## BUG mksh stdout: status=0
### exit 1 when trap code string is invalid
# All shells spew warnings to stderr, but don't actually exit! Bad!
trap 'echo <' EXIT
echo status=$?
## stdout: status=1
## BUG mksh status: 1
## BUG mksh stdout: status=0
## BUG dash/bash status: 0
## BUG dash/bash stdout: status=0
### trap EXIT
cleanup() {
echo "cleanup [$@]"
}
trap 'cleanup x y z' EXIT
## stdout: cleanup [x y z]
### trap DEBUG
debuglog() {
echo "debuglog [$@]"
}
trap 'debuglog x y' DEBUG
echo 1
echo 2
## STDOUT:
debuglog [x y]
1
debuglog [x y]
2
## END
## N-I dash/mksh STDOUT:
1
2
## END
### trap RETURN
profile() {
echo "profile [$@]"
}
g() {
echo --
echo g
echo --
return
}
f() {
echo --
echo f
echo --
g
}
# RETURN trap doesn't fire when a function returns, only when a script returns?
# That's not what the manual syas.
trap 'profile x y' RETURN
f
. spec/testdata/return-helper.sh
## status: 42
## STDOUT:
--
f
--
--
g
--
return-helper.sh
profile [x y]
## END
## N-I dash/mksh STDOUT:
--
f
--
--
g
--
return-helper.sh
## END
### trap ERR and disable it
err() {
echo "err [$@] $?"
}
trap 'err x y' ERR
echo 1
false
echo 2
trap - ERR # disable trap
false
echo 3
## STDOUT:
1
err [x y] 1
2
3
## END
# N-I dash STDOUT:
1
2
3
## END
View
@@ -289,6 +289,11 @@ builtin-test() {
${REF_SHELLS[@]} $OSH "$@"
}
builtin-trap() {
sh-spec spec/builtin-trap.test.sh --osh-failures-allowed 5 \
${REF_SHELLS[@]} $OSH "$@"
}
# Bash implements type -t, but no other shell does. For Nix.
# zsh/mksh/dash don't have the 'help' builtin.
builtin-bash() {

0 comments on commit 2cc3933

Please sign in to comment.