Skip to content

Commit fb6f3ae

Browse files
authored
Merge pull request #27 from mbland/stack-trace
Add @go.print_stack_trace to public API, show trace when module import fails
2 parents 6c44263 + 30790c9 commit fb6f3ae

File tree

6 files changed

+123
-9
lines changed

6 files changed

+123
-9
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ it easier to find, count, and possibly transform things.
310310
311311
- Use `@go.printf` for most console output to ensure that the text fits the
312312
terminal width.
313+
- Use `@go.print_stack_trace` to provide a detailed error message as
314+
appropriate, usually before calling `exit 1`.
313315
314316
### Gotchas
315317

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,9 @@ Any script in any language can invoke other command scripts by running
323323
`./go <command> [args..]`. In Bash, however, you can also invoke the `@go`
324324
function directly as `@go <command> [args...]`.
325325

326-
The `@go` and `@go.printf` functions are available to command scripts written in
327-
Bash, as Bash command scripts are sourced rather than run using another language
328-
interpreter.
326+
The `@go`, `@go.printf`, and `@go.print_stack_trace` functions are available to
327+
command scripts written in Bash, as Bash command scripts are sourced rather than
328+
run using another language interpreter.
329329

330330
A number of global variables defined and documented in `go-core.bash`, all
331331
starting with the prefix `_GO_`, are exported as environment variables and

go-core.bash

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ declare _GO_SEARCH_PATHS=("$_GO_CORE_DIR/libexec")
139139
fi
140140
}
141141

142+
# Prints the stack trace at the point of the call.
143+
#
144+
# Arguments:
145+
# omit_caller: If set, this function's caller is removed from the output
146+
@go.print_stack_trace() {
147+
local start_index="${1:+2}"
148+
local i
149+
150+
for ((i=${start_index:-1}; i != ${#FUNCNAME[@]}; ++i)); do
151+
@go.printf ' %s:%s %s\n' "${BASH_SOURCE[$i]}" "${BASH_LINENO[$((i-1))]}" \
152+
"${FUNCNAME[$i]}"
153+
done
154+
}
155+
142156
# Main driver of ./go script functionality.
143157
#
144158
# Arguments:

lib/internal/use

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,17 @@ for __go_module_name in "$@"; do
9090
__go_module_file="$_GO_SCRIPTS_DIR/lib/$__go_module_name"
9191

9292
if [[ ! -f "$__go_module_file" ]]; then
93-
@go.printf "ERROR: Unknown module: $__go_module_name\n" >&2
93+
@go.printf 'ERROR: Module %s not found at:\n' "$__go_module_name" >&2
94+
@go.print_stack_trace omit_caller >&2
9495
exit 1
9596
fi
9697
fi
9798
fi
9899

99100
if ! . "$__go_module_file"; then
100-
@go.printf "ERROR: Module import failed for: $__go_module_file\n" >&2
101+
@go.printf 'ERROR: Failed to import %s module from %s at:\n' \
102+
"$__go_module_name" "$__go_module_file" >&2
103+
@go.print_stack_trace omit_caller >&2
101104
exit 1
102105
fi
103106
done

tests/core/print-stack-trace.bats

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#! /usr/bin/env bats
2+
3+
load ../environment
4+
5+
teardown() {
6+
remove_test_go_rootdir
7+
}
8+
9+
@test "$SUITE: stack trace from top level of main ./go script" {
10+
create_test_go_script '@go.print_stack_trace'
11+
run "$TEST_GO_SCRIPT"
12+
assert_success " $TEST_GO_SCRIPT:3 main"
13+
}
14+
15+
@test "$SUITE: stack trace from top level of main ./go script without caller" {
16+
create_test_go_script '@go.print_stack_trace omit_caller'
17+
run "$TEST_GO_SCRIPT"
18+
assert_success ''
19+
}
20+
21+
@test "$SUITE: stack trace from function inside main ./go script" {
22+
create_test_go_script \
23+
'print_stack() {' \
24+
' @go.print_stack_trace' \
25+
'}' \
26+
'print_stack'
27+
run "$TEST_GO_SCRIPT"
28+
29+
local expected=(" $TEST_GO_SCRIPT:4 print_stack"
30+
" $TEST_GO_SCRIPT:6 main")
31+
local IFS=$'\n'
32+
assert_success "${expected[*]}"
33+
}
34+
35+
@test "$SUITE: omit function caller from stack trace" {
36+
create_test_go_script \
37+
'print_stack() {' \
38+
" @go.print_stack_trace omit_caller" \
39+
'}' \
40+
'print_stack'
41+
run "$TEST_GO_SCRIPT"
42+
assert_success " $TEST_GO_SCRIPT:6 main"
43+
}
44+
45+
@test "$SUITE: stack trace from subcommand script" {
46+
create_test_go_script '@go "$@"'
47+
create_test_command_script 'foo' \
48+
'foo_func() {' \
49+
' @go foo bar' \
50+
'}' \
51+
'foo_func'
52+
create_test_command_script 'foo.d/bar' \
53+
'bar_func() {' \
54+
' @go.print_stack_trace omit_caller' \
55+
'}' \
56+
'bar_func'
57+
58+
run "$TEST_GO_SCRIPT" foo
59+
60+
local go_core_pattern="$_GO_CORE_DIR/go-core.bash:[0-9]+"
61+
assert_success
62+
assert_line_equals 0 " $TEST_GO_SCRIPTS_DIR/foo.d/bar:5 source"
63+
assert_line_matches 1 " $go_core_pattern [email protected]_command_script"
64+
assert_line_matches 2 " $go_core_pattern @go"
65+
assert_line_equals 3 " $TEST_GO_SCRIPTS_DIR/foo:3 foo_func"
66+
assert_line_equals 4 " $TEST_GO_SCRIPTS_DIR/foo:5 source"
67+
assert_line_matches 5 " $go_core_pattern [email protected]_command_script"
68+
assert_line_matches 6 " $go_core_pattern @go"
69+
assert_line_equals 7 " $TEST_GO_SCRIPT:3 main"
70+
}

tests/modules/use.bats

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ teardown() {
4545

4646
@test "$SUITE: error if nonexistent module specified" {
4747
run "$TEST_GO_SCRIPT" 'bogus-test-module'
48-
assert_failure 'ERROR: Unknown module: bogus-test-module'
48+
49+
local expected=('ERROR: Module bogus-test-module not found at:'
50+
" $TEST_GO_SCRIPT:3 main")
51+
local IFS=$'\n'
52+
assert_failure "${expected[*]}"
4953
}
5054

5155
@test "$SUITE: import modules successfully" {
@@ -73,12 +77,33 @@ teardown() {
7377
}
7478

7579
@test "$SUITE: error if module contains errors" {
76-
echo "This is a totally broken module." >> "${TEST_MODULES[1]}"
80+
local module="${IMPORTS[1]}"
81+
local module_file="${TEST_MODULES[2]}"
82+
83+
echo "This is a totally broken module." > "$module_file"
84+
run "$TEST_GO_SCRIPT" "${IMPORTS[@]}"
85+
86+
local expected=("${IMPORTS[0]##*/} loaded"
87+
"$module_file: line 1: This: command not found"
88+
"ERROR: Failed to import $module module from $module_file at:"
89+
" $TEST_GO_SCRIPT:3 main")
90+
local IFS=$'\n'
91+
assert_failure "${expected[*]}"
92+
}
93+
94+
@test "$SUITE: error if module returns an error" {
95+
local module="${IMPORTS[1]}"
96+
local module_file="${TEST_MODULES[2]}"
97+
local error_message='These violent delights have violent ends...'
98+
99+
echo "echo '$error_message' >&2" > "$module_file"
100+
echo "return 1" >> "$module_file"
77101
run "$TEST_GO_SCRIPT" "${IMPORTS[@]}"
78102

79103
local expected=("${IMPORTS[0]##*/} loaded"
80-
"${TEST_MODULES[1]}: line 2: This: command not found"
81-
"ERROR: Module import failed for: ${TEST_MODULES[1]}")
104+
"$error_message"
105+
"ERROR: Failed to import $module module from $module_file at:"
106+
" $TEST_GO_SCRIPT:3 main")
82107
local IFS=$'\n'
83108
assert_failure "${expected[*]}"
84109
}

0 commit comments

Comments
 (0)