Skip to content

Commit

Permalink
Merge pull request #27 from mbland/stack-trace
Browse files Browse the repository at this point in the history
Add @go.print_stack_trace to public API, show trace when module import fails
  • Loading branch information
mbland authored Dec 14, 2016
2 parents 6c44263 + 30790c9 commit fb6f3ae
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ it easier to find, count, and possibly transform things.
- Use `@go.printf` for most console output to ensure that the text fits the
terminal width.
- Use `@go.print_stack_trace` to provide a detailed error message as
appropriate, usually before calling `exit 1`.
### Gotchas
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,9 @@ Any script in any language can invoke other command scripts by running
`./go <command> [args..]`. In Bash, however, you can also invoke the `@go`
function directly as `@go <command> [args...]`.

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

A number of global variables defined and documented in `go-core.bash`, all
starting with the prefix `_GO_`, are exported as environment variables and
Expand Down
14 changes: 14 additions & 0 deletions go-core.bash
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,20 @@ declare _GO_SEARCH_PATHS=("$_GO_CORE_DIR/libexec")
fi
}

# Prints the stack trace at the point of the call.
#
# Arguments:
# omit_caller: If set, this function's caller is removed from the output
@go.print_stack_trace() {
local start_index="${1:+2}"
local i

for ((i=${start_index:-1}; i != ${#FUNCNAME[@]}; ++i)); do
@go.printf ' %s:%s %s\n' "${BASH_SOURCE[$i]}" "${BASH_LINENO[$((i-1))]}" \
"${FUNCNAME[$i]}"
done
}

# Main driver of ./go script functionality.
#
# Arguments:
Expand Down
7 changes: 5 additions & 2 deletions lib/internal/use
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,17 @@ for __go_module_name in "$@"; do
__go_module_file="$_GO_SCRIPTS_DIR/lib/$__go_module_name"

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

if ! . "$__go_module_file"; then
@go.printf "ERROR: Module import failed for: $__go_module_file\n" >&2
@go.printf 'ERROR: Failed to import %s module from %s at:\n' \
"$__go_module_name" "$__go_module_file" >&2
@go.print_stack_trace omit_caller >&2
exit 1
fi
done
Expand Down
70 changes: 70 additions & 0 deletions tests/core/print-stack-trace.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#! /usr/bin/env bats

load ../environment

teardown() {
remove_test_go_rootdir
}

@test "$SUITE: stack trace from top level of main ./go script" {
create_test_go_script '@go.print_stack_trace'
run "$TEST_GO_SCRIPT"
assert_success " $TEST_GO_SCRIPT:3 main"
}

@test "$SUITE: stack trace from top level of main ./go script without caller" {
create_test_go_script '@go.print_stack_trace omit_caller'
run "$TEST_GO_SCRIPT"
assert_success ''
}

@test "$SUITE: stack trace from function inside main ./go script" {
create_test_go_script \
'print_stack() {' \
' @go.print_stack_trace' \
'}' \
'print_stack'
run "$TEST_GO_SCRIPT"

local expected=(" $TEST_GO_SCRIPT:4 print_stack"
" $TEST_GO_SCRIPT:6 main")
local IFS=$'\n'
assert_success "${expected[*]}"
}

@test "$SUITE: omit function caller from stack trace" {
create_test_go_script \
'print_stack() {' \
" @go.print_stack_trace omit_caller" \
'}' \
'print_stack'
run "$TEST_GO_SCRIPT"
assert_success " $TEST_GO_SCRIPT:6 main"
}

@test "$SUITE: stack trace from subcommand script" {
create_test_go_script '@go "$@"'
create_test_command_script 'foo' \
'foo_func() {' \
' @go foo bar' \
'}' \
'foo_func'
create_test_command_script 'foo.d/bar' \
'bar_func() {' \
' @go.print_stack_trace omit_caller' \
'}' \
'bar_func'

run "$TEST_GO_SCRIPT" foo

local go_core_pattern="$_GO_CORE_DIR/go-core.bash:[0-9]+"
assert_success
assert_line_equals 0 " $TEST_GO_SCRIPTS_DIR/foo.d/bar:5 source"
assert_line_matches 1 " $go_core_pattern [email protected]_command_script"
assert_line_matches 2 " $go_core_pattern @go"
assert_line_equals 3 " $TEST_GO_SCRIPTS_DIR/foo:3 foo_func"
assert_line_equals 4 " $TEST_GO_SCRIPTS_DIR/foo:5 source"
assert_line_matches 5 " $go_core_pattern [email protected]_command_script"
assert_line_matches 6 " $go_core_pattern @go"
assert_line_equals 7 " $TEST_GO_SCRIPT:3 main"
}
33 changes: 29 additions & 4 deletions tests/modules/use.bats
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ teardown() {

@test "$SUITE: error if nonexistent module specified" {
run "$TEST_GO_SCRIPT" 'bogus-test-module'
assert_failure 'ERROR: Unknown module: bogus-test-module'

local expected=('ERROR: Module bogus-test-module not found at:'
" $TEST_GO_SCRIPT:3 main")
local IFS=$'\n'
assert_failure "${expected[*]}"
}

@test "$SUITE: import modules successfully" {
Expand Down Expand Up @@ -73,12 +77,33 @@ teardown() {
}

@test "$SUITE: error if module contains errors" {
echo "This is a totally broken module." >> "${TEST_MODULES[1]}"
local module="${IMPORTS[1]}"
local module_file="${TEST_MODULES[2]}"

echo "This is a totally broken module." > "$module_file"
run "$TEST_GO_SCRIPT" "${IMPORTS[@]}"

local expected=("${IMPORTS[0]##*/} loaded"
"$module_file: line 1: This: command not found"
"ERROR: Failed to import $module module from $module_file at:"
" $TEST_GO_SCRIPT:3 main")
local IFS=$'\n'
assert_failure "${expected[*]}"
}

@test "$SUITE: error if module returns an error" {
local module="${IMPORTS[1]}"
local module_file="${TEST_MODULES[2]}"
local error_message='These violent delights have violent ends...'

echo "echo '$error_message' >&2" > "$module_file"
echo "return 1" >> "$module_file"
run "$TEST_GO_SCRIPT" "${IMPORTS[@]}"

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

0 comments on commit fb6f3ae

Please sign in to comment.