Skip to content

Commit

Permalink
Implement dynamic test registration
Browse files Browse the repository at this point in the history
- simplify stack trace limiting
  • Loading branch information
martin-schulze-vireso committed Dec 25, 2023
1 parent aba31fc commit 9d5ecdb
Show file tree
Hide file tree
Showing 12 changed files with 651 additions and 382 deletions.
54 changes: 37 additions & 17 deletions lib/bats-core/test_functions.bash
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#!/usr/bin/env bash

BATS_TEST_DIRNAME="${BATS_TEST_FILENAME%/*}"
BATS_TEST_NAMES=()
# this must be called for each test file!
_bats_test_functions_setup() { # <BATS_TEST_NUMBER>
BATS_TEST_DIRNAME="${BATS_TEST_FILENAME%/*}"
BATS_TEST_NAMES=()
BATS_TEST_NUMBER=${1?}
}

# shellcheck source=lib/bats-core/warnings.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/warnings.bash"
Expand Down Expand Up @@ -462,25 +466,41 @@ skip() {
exit 0
}

bats_test_begin() {
BATS_TEST_DESCRIPTION="$1"
if [[ -n "$BATS_EXTENDED_SYNTAX" ]]; then
printf 'begin %d %s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}$BATS_TEST_DESCRIPTION" >&3
fi
setup
}

bats_test_function() {
local tags=()
if [[ "$1" == --tags ]]; then
IFS=',' read -ra tags <<<"$2"
shift 2
fi
local test_name="$1"
BATS_TEST_NAMES+=("$test_name")
if [[ "$test_name" == "$BATS_TEST_NAME" ]]; then
local got_test_description=
while (( $# > 0 )); do
case "$1" in
--description)
local test_description=
# use eval to resolve variable references in test names
eval "printf -v test_description '%s' \"$2\""
got_test_description=1
shift 2
;;
--tags)
IFS=',' read -ra tags <<<"$2"
shift 2
;;
--)
shift
break
;;
*)
printf "ERROR: unknown option %s for bats_test_function" "$1" >&2
exit 1
;;
esac
done

BATS_TEST_NAMES+=("$*")
# if this is the currently selected test, set tags and name
# this should only be entered from bats-exec-test
if [[ ${BATS_TEST_NAME-} == $* ]]; then
# shellcheck disable=SC2034
BATS_TEST_TAGS=("${tags[@]+${tags[@]}}")
export BATS_TEST_DESCRIPTION="${test_description-$*}"
BATS_TEST_COMMAND=("$@")
fi
}

Expand Down
77 changes: 50 additions & 27 deletions lib/bats-core/tracing.bash
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash"

# set limit such that traces are only captured for calls at the same depth as this function in the calltree
bats_set_stacktrace_limit() { # [<offset>]
local -i offset=${1-0}
BATS_STACK_TRACE_LIMIT=$(( ${#FUNCNAME[@]} + offset -1 )) # adjust by -1 to account for call to this functions
}

bats_capture_stack_trace() {
local test_file
local funcname
local i

BATS_DEBUG_LAST_STACK_TRACE=()

for ((i = 2; i != ${#FUNCNAME[@]}; ++i)); do
local limit=$(( ${#FUNCNAME[@]} - ${BATS_STACK_TRACE_LIMIT-0} ))
# TODO: why is the line number off by one in @test "--trace recurses into functions but not into run"
for ((i = 2; i < limit ; ++i)); do
# Use BATS_TEST_SOURCE if necessary to work around Bash < 4.4 bug whereby
# calling an exported function erases the test file's BASH_SOURCE entry.
test_file="${BASH_SOURCE[$i]:-$BATS_TEST_SOURCE}"
funcname="${FUNCNAME[$i]}"
BATS_DEBUG_LAST_STACK_TRACE+=("${BASH_LINENO[$((i - 1))]} $funcname $test_file")
case "$funcname" in
"${BATS_TEST_NAME-}" | setup | teardown | setup_file | teardown_file | setup_suite | teardown_suite)
break
;;
esac
if [[ "${BASH_SOURCE[$i + 1]:-}" == *"bats-exec-file" ]] && [[ "$funcname" == 'source' ]]; then
break
fi
done
}

Expand Down Expand Up @@ -175,30 +174,55 @@ bats_normalize_windows_dir_path() { # <output-var> <path>
printf -v "$output_var" "%s" "$NORMALIZED_INPUT"
}

bats_emit_trace_context() {
local padding='$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$'
local reference
bats_format_file_line_reference reference "${file##*/}" "$line"
printf '%s [%s]\n' "${padding::${#BASH_LINENO[@]}-limit-3}" "$reference" >&4
}

bats_emit_trace_command() {
local padding='$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$'
printf '%s %s\n' "${padding::${#BASH_LINENO[@]}-limit-3}" "$BASH_COMMAND" >&4

# keep track of printed commands
BATS_LAST_BASH_COMMAND="$BASH_COMMAND"
BATS_LAST_BASH_LINENO="$line"
}

BATS_EMIT_TRACE_LAST_STACK_DIFF=0

bats_emit_trace() {
if [[ $BATS_TRACE_LEVEL -gt 0 ]]; then
local line=${BASH_LINENO[1]}
if [[ ${BATS_TRACE_LEVEL:-0} -gt 0 ]]; then
local line=${BASH_LINENO[1]} limit=${BATS_STACK_TRACE_LIMIT-0}
# shellcheck disable=SC2016
if [[ $BASH_COMMAND != '"$BATS_TEST_NAME" >> "$BATS_OUT" 2>&1 4>&1' && $BASH_COMMAND != "bats_test_begin "* ]] && # don't emit these internal calls
[[ $BASH_COMMAND != "$BATS_LAST_BASH_COMMAND" || $line != "$BATS_LAST_BASH_LINENO" ]] &&
# avoid printing a function twice (at call site and at definition site)
[[ $BASH_COMMAND != "$BATS_LAST_BASH_COMMAND" || ${BASH_LINENO[2]} != "$BATS_LAST_BASH_LINENO" || ${BASH_SOURCE[3]} != "$BATS_LAST_BASH_SOURCE" ]]; then
if (( ${#FUNCNAME[@]} > limit + 2 )) # only emit below BATS_STRACK_TRACE_LIMIT (adjust by 2 for trap+this function call)
# avoid printing the same line twice on errexit
[[ $BASH_COMMAND != "$BATS_LAST_BASH_COMMAND" || $line != "$BATS_LAST_BASH_LINENO" ]]; then
local file="${BASH_SOURCE[2]}" # index 2: skip over bats_emit_trace and bats_debug_trap
if [[ $file == "${BATS_TEST_SOURCE:-}" ]]; then
file="$BATS_TEST_FILENAME"
fi
local padding='$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$'
if ((BATS_LAST_STACK_DEPTH != ${#BASH_LINENO[@]})); then
local reference
bats_format_file_line_reference reference "${file##*/}" "$line"
printf '%s [%s]\n' "${padding::${#BASH_LINENO[@]}-4}" "$reference" >&4
# stack size difference since last call of this function
# <0: means new function call
# >0: means return
# =0: in same function as before (assuming we did not skip return/call)
local stack_diff=$(( BATS_LAST_STACK_DEPTH - ${#BASH_LINENO[@]} ))
# show context immediately when returning or on second command in new function
# as the first "command" is the function itself
if (( stack_diff > 0 )) || (( BATS_EMIT_TRACE_LAST_STACK_DIFF < 0 )); then
bats_emit_trace_context
fi
# only print command when moving up or staying in same function
# again, avoids printing the first command (the function itself) in new function
if (( stack_diff >= 0 )); then
bats_emit_trace_command
fi
printf '%s %s\n' "${padding::${#BASH_LINENO[@]}-4}" "$BASH_COMMAND" >&4
BATS_LAST_BASH_COMMAND="$BASH_COMMAND"
BATS_LAST_BASH_LINENO="$line"
BATS_LAST_BASH_SOURCE="${BASH_SOURCE[2]}"
BATS_LAST_STACK_DEPTH="${#BASH_LINENO[@]}"

BATS_EMIT_TRACE_LAST_STACK_DIFF=$stack_diff
fi
# always update to detect stack depth changes regardless of printing
BATS_LAST_STACK_DEPTH="${#BASH_LINENO[@]}"
fi
}

Expand Down Expand Up @@ -344,7 +368,6 @@ bats_setup_tracing() {
# avoid undefined variable errors
BATS_LAST_BASH_COMMAND=
BATS_LAST_BASH_LINENO=
BATS_LAST_BASH_SOURCE=
BATS_LAST_STACK_DEPTH=
# try to exclude helper libraries if found, this is only relevant for tracing
while read -r path; do
Expand Down
8 changes: 7 additions & 1 deletion libexec/bats-core/bats-exec-file
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ bats_run_setup_file() {
# shellcheck disable=SC2153
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/test_functions.bash"

_bats_test_functions_setup -1 # invalid TEST_NUMBER, as this is not a test

exec 3<&1

# these are defined only to avoid errors when referencing undefined variables down the line
Expand All @@ -96,6 +98,7 @@ bats_run_setup_file() {

BATS_SOURCE_FILE_COMPLETED=1

bats_set_stacktrace_limit
setup_file >>"$BATS_OUT" 2>&1

BATS_SETUP_FILE_COMPLETED=1
Expand All @@ -105,6 +108,8 @@ bats_run_teardown_file() {
local bats_teardown_file_status=0
# avoid running the therdown trap due to errors in teardown_file
trap 'bats_file_exit_trap' EXIT

bats_set_stacktrace_limit

# rely on bats_error_trap to catch failures
teardown_file >>"$BATS_OUT" 2>&1 || bats_teardown_file_status=$?
Expand Down Expand Up @@ -292,7 +297,7 @@ bats_read_tests_list_file() {

bats_run_tests() {
bats_exec_file_status=0

# TODO: this does not seem to be used anymore?
if [[ "${BATS_RUN_TESTS_SKIPPED-}" ]]; then
# shellcheck disable=SC2317
bats_test_begin() {
Expand All @@ -319,6 +324,7 @@ bats_run_tests() {
local test_number_in_suite=$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE \
test_number_in_file=0
for test_name in "${tests_to_run[@]}"; do
#echo "bats-exec-file: execute test ${test_name}" >&2
if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
bats_exec_file_status=130 # bash's code for SIGINT exits
break
Expand Down

0 comments on commit 9d5ecdb

Please sign in to comment.