#!/usr/bin/env bash set -e set -E set -T BATS_COUNT_ONLY="" if [ "$1" = "-c" ]; then BATS_COUNT_ONLY=1 shift fi BATS_EXTENDED_SYNTAX="" if [ "$1" = "-x" ]; then BATS_EXTENDED_SYNTAX="$1" shift fi BATS_TEST_FILENAME="$1" if [ -z "$BATS_TEST_FILENAME" ]; then echo "usage: bats-exec " >&2 exit 1 elif [ ! -f "$BATS_TEST_FILENAME" ]; then echo "bats: $BATS_TEST_FILENAME does not exist" >&2 exit 1 else shift fi BATS_TEST_DIRNAME="${BATS_TEST_FILENAME%/*}" BATS_TEST_NAMES=() load() { local name="$1" local filename # Argument is absolute, verify that the path exists and return after # sourcing if [[ "${name:0:1}" == "/" ]]; then filename=$name if [[ ! -f "$filename" ]]; then echo "bats: $filename does not exist" >&2 exit 1 fi source "$filename" return fi # Set libpath with directory of current test file # Defaults to BATS_LIB_PATH e.g. for testing local libpath="${BATS_LIB_PATH:-$HOME/.bats/lib:/usr/lib/bats}" libpath="$BATS_TEST_DIRNAME:$libpath" # Test for library file in each libpath, source and return if it # exists for part in ${libpath//:/ }; do filename="$part/$name.bash" if [[ -f "$filename" ]]; then source "$filename" return fi done echo "bats: No file $name in BATS_LIB_PATH found" >&2 exit 1 } run() { local e E T oldIFS [[ ! "$-" =~ e ]] || e=1 [[ ! "$-" =~ E ]] || E=1 [[ ! "$-" =~ T ]] || T=1 set +e set +E set +T output="$("$@" 2>&1)" status="$?" oldIFS=$IFS IFS=$'\n' lines=($output) [ -z "$e" ] || set -e [ -z "$E" ] || set -E [ -z "$T" ] || set -T IFS=$oldIFS } setup() { true } teardown() { true } skip() { BATS_TEST_SKIPPED=${1:-1} BATS_TEST_COMPLETED=1 exit 0 } bats_test_begin() { BATS_TEST_DESCRIPTION="$1" if [ -n "$BATS_EXTENDED_SYNTAX" ]; then echo "begin $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3 fi setup } bats_test_function() { local test_name="$1" BATS_TEST_NAMES["${#BATS_TEST_NAMES[@]}"]="$test_name" } bats_capture_stack_trace() { BATS_PREVIOUS_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" ) BATS_CURRENT_STACK_TRACE=() local test_pattern=" $BATS_TEST_NAME $BATS_TEST_SOURCE" local setup_pattern=" setup $BATS_TEST_SOURCE" local teardown_pattern=" teardown $BATS_TEST_SOURCE" local frame local i for ((i=2; i != ${#FUNCNAME[@]}; ++i)); do frame="${BASH_LINENO[$((i-1))]} ${FUNCNAME[$i]} ${BASH_SOURCE[$i]}" BATS_CURRENT_STACK_TRACE["${#BATS_CURRENT_STACK_TRACE[@]}"]="$frame" if [[ "$frame" = *"$test_pattern" || \ "$frame" = *"$setup_pattern" || \ "$frame" = *"$teardown_pattern" ]]; then break fi done bats_frame_filename "${BATS_CURRENT_STACK_TRACE[0]}" 'BATS_SOURCE' bats_frame_lineno "${BATS_CURRENT_STACK_TRACE[0]}" 'BATS_LINENO' } bats_print_stack_trace() { local frame local index=1 local count="${#@}" local filename local lineno for frame in "$@"; do bats_frame_filename "$frame" 'filename' bats_trim_filename "$filename" 'filename' bats_frame_lineno "$frame" 'lineno' if [ $index -eq 1 ]; then echo -n "# (" else echo -n "# " fi local fn bats_frame_function "$frame" 'fn' if [ "$fn" != "$BATS_TEST_NAME" ]; then echo -n "from function \`$fn' " fi if [ $index -eq $count ]; then echo "in test file $filename, line $lineno)" else echo "in file $filename, line $lineno," fi let index+=1 done } bats_print_failed_command() { local frame="$1" local status="$2" local filename local lineno local failed_line local failed_command bats_frame_filename "$frame" 'filename' bats_frame_lineno "$frame" 'lineno' bats_extract_line "$filename" "$lineno" 'failed_line' bats_strip_string "$failed_line" 'failed_command' printf '%s' "# \`${failed_command}' " if [ $status -eq 1 ]; then echo "failed" else echo "failed with status $status" fi } bats_frame_lineno() { printf -v "$2" '%s' "${1%% *}" } bats_frame_function() { local __bff_function="${1#* }" printf -v "$2" '%s' "${__bff_function%% *}" } bats_frame_filename() { local __bff_filename="${1#* }" __bff_filename="${__bff_filename#* }" if [ "$__bff_filename" = "$BATS_TEST_SOURCE" ]; then __bff_filename="$BATS_TEST_FILENAME" fi printf -v "$2" '%s' "$__bff_filename" } bats_extract_line() { local __bats_extract_line_line local __bats_extract_line_index='0' while IFS= read -r __bats_extract_line_line; do if [[ "$((++__bats_extract_line_index))" -eq "$2" ]]; then printf -v "$3" '%s' "${__bats_extract_line_line%$'\r'}" break fi done <"$1" } bats_strip_string() { [[ "$1" =~ ^[[:space:]]*(.*)[[:space:]]*$ ]] printf -v "$2" '%s' "${BASH_REMATCH[1]}" } bats_trim_filename() { if [[ "$1" =~ ^${BATS_CWD}/ ]]; then printf -v "$2" '%s' "${1#$BATS_CWD/}" else printf -v "$2" '%s' "$1" fi } bats_debug_trap() { if [ "$BASH_SOURCE" != "$1" ]; then bats_capture_stack_trace fi } # When running under Bash 3.2.57(1)-release on macOS, the `ERR` trap may not # always fire, but the `EXIT` trap will. For this reason we call it at the very # beginning of `bats_teardown_trap` (the `DEBUG` trap for the call will move # `BATS_CURRENT_STACK_TRACE` to `BATS_PREVIOUS_STACK_TRACE`) and check the value # of `$?` before taking other actions. bats_error_trap() { local status="$?" if [[ "$status" -ne '0' ]]; then BATS_ERROR_STATUS="$status" BATS_ERROR_STACK_TRACE=( "${BATS_PREVIOUS_STACK_TRACE[@]}" ) trap - debug fi } bats_teardown_trap() { bats_error_trap trap "bats_exit_trap" exit local status=0 teardown >>"$BATS_OUT" 2>&1 || status="$?" if [ $status -eq 0 ]; then BATS_TEARDOWN_COMPLETED=1 elif [ -n "$BATS_TEST_COMPLETED" ]; then BATS_ERROR_STATUS="$status" BATS_ERROR_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" ) fi bats_exit_trap } bats_exit_trap() { local status local skipped trap - err exit if [ -n "$BATS_TEST_SKIPPED" ]; then skipped=" # skip" if [ "1" != "$BATS_TEST_SKIPPED" ]; then skipped+=" $BATS_TEST_SKIPPED" fi fi if [ -z "$BATS_TEST_COMPLETED" ] || [ -z "$BATS_TEARDOWN_COMPLETED" ]; then echo "not ok $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3 bats_print_stack_trace "${BATS_ERROR_STACK_TRACE[@]}" >&3 bats_print_failed_command "${BATS_ERROR_STACK_TRACE[${#BATS_ERROR_STACK_TRACE[@]}-1]}" "$BATS_ERROR_STATUS" >&3 sed -e "s/^/# /" < "$BATS_OUT" >&3 status=1 else echo "ok ${BATS_TEST_NUMBER} ${BATS_TEST_DESCRIPTION}${skipped}" >&3 status=0 fi rm -f "$BATS_OUT" exit "$status" } bats_perform_tests() { echo "1..$#" test_number=1 status=0 for test_name in "$@"; do "$0" $BATS_EXTENDED_SYNTAX "$BATS_TEST_FILENAME" "$test_name" "$test_number" || status=1 let test_number+=1 done exit "$status" } bats_perform_test() { BATS_TEST_NAME="$1" if declare -F "$BATS_TEST_NAME" >/dev/null; then BATS_TEST_NUMBER="$2" if [ -z "$BATS_TEST_NUMBER" ]; then echo "1..1" BATS_TEST_NUMBER="1" fi BATS_TEST_COMPLETED="" BATS_TEARDOWN_COMPLETED="" trap "bats_debug_trap \"\$BASH_SOURCE\"" debug trap "bats_error_trap" err trap "bats_teardown_trap" exit "$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1 BATS_TEST_COMPLETED=1 else echo "bats: unknown test name \`$BATS_TEST_NAME'" >&2 exit 1 fi } if [ -z "$TMPDIR" ]; then BATS_TMPDIR="/tmp" else BATS_TMPDIR="${TMPDIR%/}" fi BATS_TMPNAME="$BATS_TMPDIR/bats.$$" BATS_PARENT_TMPNAME="$BATS_TMPDIR/bats.$PPID" BATS_OUT="${BATS_TMPNAME}.out" bats_preprocess_source() { BATS_TEST_SOURCE="${BATS_TMPNAME}.src" . bats-preprocess <<< "$(< "$BATS_TEST_FILENAME")"$'\n' > "$BATS_TEST_SOURCE" trap "bats_cleanup_preprocessed_source" err exit trap "bats_cleanup_preprocessed_source; exit 1" int } bats_cleanup_preprocessed_source() { rm -f "$BATS_TEST_SOURCE" } bats_evaluate_preprocessed_source() { if [ -z "$BATS_TEST_SOURCE" ]; then BATS_TEST_SOURCE="${BATS_PARENT_TMPNAME}.src" fi source "$BATS_TEST_SOURCE" } exec 3<&1 if [ "$#" -eq 0 ]; then bats_preprocess_source bats_evaluate_preprocessed_source if [ -n "$BATS_COUNT_ONLY" ]; then echo "${#BATS_TEST_NAMES[@]}" else bats_perform_tests "${BATS_TEST_NAMES[@]}" fi else bats_evaluate_preprocessed_source bats_perform_test "$@" fi