This commit is contained in:
2026-08-02 15:26:07 +02:00
parent 336b36c8e1
commit b2eb145a6f
415 changed files with 24264 additions and 36 deletions
+517
View File
@@ -0,0 +1,517 @@
#!/usr/bin/env bash
set -e
export BATS_VERSION='1.11.0'
VALID_FORMATTERS="pretty, junit, tap, tap13"
version() {
printf 'Bats %s\n' "$BATS_VERSION"
}
abort() {
local print_usage=1
if [[ ${1:-} == --no-print-usage ]]; then
print_usage=
shift
fi
printf 'Error: %s\n' "$1" >&2
if [[ -n $print_usage ]]; then
usage >&2
fi
exit 1
}
usage() {
local cmd="${0##*/}"
local line
cat <<HELP_TEXT_HEADER
Usage: ${cmd} [OPTIONS] <tests>
${cmd} [-h | -v]
HELP_TEXT_HEADER
cat <<'HELP_TEXT_BODY'
<tests> is the path to a Bats test file, or the path to a directory
containing Bats test files (ending with ".bats")
-c, --count Count test cases without running any tests
--code-quote-style <style>
A two character string of code quote delimiters
or 'custom' which requires setting $BATS_BEGIN_CODE_QUOTE and
$BATS_END_CODE_QUOTE. Can also be set via $BATS_CODE_QUOTE_STYLE
--line-reference-format Controls how file/line references e.g. in stack traces are printed:
- comma_line (default): a.bats, line 1
- colon: a.bats:1
- uri: file:///tests/a.bats:1
- custom: provide your own via defining bats_format_file_line_reference_custom
with parameters <filename> <line>, store via `printf -v "$output"`
-f, --filter <regex> Only run tests that match the regular expression
--filter-status <status> Only run tests with the given status in the last completed (no CTRL+C/SIGINT) run.
Valid <status> values are:
failed - runs tests that failed or were not present in the last run
missed - runs tests that were not present in the last run
--filter-tags <comma-separated-tag-list>
Only run tests that match all the tags in the list (&&).
You can negate a tag via prepending '!'.
Specifying this flag multiple times allows for logical or (||):
`--filter-tags A,B --filter-tags A,!C` matches tags (A && B) || (A && !C)
-F, --formatter <type> Switch between formatters: pretty (default),
tap (default w/o term), tap13, junit, /<absolute path to formatter>
--gather-test-outputs-in <directory>
Gather the output of failing *and* passing tests
as files in directory (if existing, must be empty)
-h, --help Display this help message
-j, --jobs <jobs> Number of parallel jobs (requires GNU parallel or shenwei356/rush)
--parallel-binary-name Name of parallel binary
--no-tempdir-cleanup Preserve test output temporary directory
--no-parallelize-across-files
Serialize test file execution instead of running
them in parallel (requires --jobs >1)
--no-parallelize-within-files
Serialize test execution within files instead of
running them in parallel (requires --jobs >1)
--report-formatter <type> Switch between reporters (same options as --formatter)
-o, --output <dir> Directory to write report files (must exist)
-p, --pretty Shorthand for "--formatter pretty"
--print-output-on-failure Automatically print the value of `$output` on failed tests
-r, --recursive Include tests in subdirectories
--show-output-of-passing-tests
Print output of passing tests
-t, --tap Shorthand for "--formatter tap"
-T, --timing Add timing information to tests
-x, --trace Print test commands as they are executed (like `set -x`)
--verbose-run Make `run` print `$output` by default
-v, --version Display the version number
For more information, see https://github.com/bats-core/bats-core
HELP_TEXT_BODY
}
expand_path() {
local path="${1%/}"
local dirname="${path%/*}"
if [[ -z "$dirname" ]]; then
dirname=/
fi
local result="$2"
local OLDPWD="$PWD"
if [[ "$dirname" == "$path" ]]; then
dirname="$PWD"
else
cd "$dirname"
dirname="$PWD"
cd "$OLDPWD"
fi
printf -v "$result" '%s/%s' "$dirname" "${path##*/}"
}
BATS_LIBEXEC="$(
cd "$(dirname "$(bats_readlinkf "${BASH_SOURCE[0]}")")"
pwd
)"
export BATS_LIBEXEC
export BATS_CWD="$PWD"
export BATS_TEST_FILTER=
export PATH="$BATS_LIBEXEC:$PATH"
export BATS_ROOT_PID=$$
export BATS_TMPDIR="${TMPDIR:-/tmp}"
BATS_TMPDIR=${BATS_TMPDIR%/} # chop off trailing / to avoid duplication
export BATS_RUN_TMPDIR=
export BATS_GUARANTEED_MINIMUM_VERSION=0.0.0
export BATS_LIB_PATH=${BATS_LIB_PATH-/usr/lib/bats}
BATS_REPORT_OUTPUT_DIR=${BATS_REPORT_OUTPUT_DIR-.}
export BATS_LINE_REFERENCE_FORMAT=${BATS_LINE_REFERENCE_FORMAT-comma_line}
if [[ ! -d "${BATS_TMPDIR}" ]]; then
printf "Error: BATS_TMPDIR (%s) does not exist or is not a directory" "${BATS_TMPDIR}" >&2
exit 1
elif [[ ! -w "${BATS_TMPDIR}" ]]; then
printf "Error: BATS_TMPDIR (%s) is not writable" "${BATS_TMPDIR}" >&2
exit 1
fi
arguments=()
# list of single char options that don't expect a value
single_char_flags="hvcprtTx"
# Unpack single-character options bundled together, e.g. -cr, -pr.
for arg in "$@"; do
if [[ "$arg" =~ ^-[^-]. ]]; then
index=1
while option="${arg:$((index++)):1}"; do
if [[ -z "$option" ]]; then
break
fi
if [[ "$single_char_flags" != *$option* && -n "${arg:index}" ]]; then
printf "Error: -%s is not allowed within pack of flags.\n" "$option"
printf " Please put it last (e.g. \`-rF junit\` instead of \`-Fr junit\`), or on its own (\`-r -F junit\`)!\n"
exit 1
fi
arguments+=("-$option")
done
else
arguments+=("$arg")
fi
shift
done
set -- "${arguments[@]}"
arguments=()
unset flags recursive formatter_flags
flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
formatter_flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
formatter=${BATS_FORMATTER:-'tap'}
report_formatter=''
recursive=
setup_suite_file=''
export BATS_TEMPDIR_CLEANUP=1
if [[ -z "${CI:-}" && -t 0 && -t 1 ]] && command -v tput >/dev/null; then
formatter='pretty'
fi
while [[ "$#" -ne 0 ]]; do
case "$1" in
-h | --help)
version
usage
exit 0
;;
-v | --version)
version
exit 0
;;
-c | --count)
flags+=('-c')
;;
-f | --filter)
shift
flags+=('-f' "$1")
;;
-F | --formatter)
shift
# allow cat formatter to see extended output but don't advertise to users
if [[ $1 =~ ^(pretty|junit|tap|tap13|cat|/.*)$ ]]; then
formatter="$1"
else
printf "Unknown formatter '%s', valid options are %s\n" "$1" "${VALID_FORMATTERS}"
exit 1
fi
;;
--report-formatter)
shift
if [[ $1 =~ ^(cat|pretty|junit|tap|tap13|/.*)$ ]]; then
report_formatter="$1"
else
printf "Unknown report formatter '%s', valid options are %s\n" "$1" "${VALID_FORMATTERS}"
exit 1
fi
;;
-o | --output)
shift
BATS_REPORT_OUTPUT_DIR="$1"
;;
-p | --pretty)
formatter='pretty'
;;
-j | --jobs)
shift
flags+=('-j' "$1")
;;
--parallel-binary-name)
shift
flags+=('--parallel-binary-name' "$1")
;;
-r | --recursive)
recursive=1
;;
-t | --tap)
formatter='tap'
;;
-T | --timing)
flags+=('-T')
formatter_flags+=('-T')
;;
# this flag is now a no-op, as it is the parallel default
--parallel-preserve-environment) ;;
--no-parallelize-across-files)
flags+=("--no-parallelize-across-files")
;;
--no-parallelize-within-files)
flags+=("--no-parallelize-within-files")
;;
--no-tempdir-cleanup)
BATS_TEMPDIR_CLEANUP=''
;;
--tempdir) # for internal test consumption only!
BATS_RUN_TMPDIR="$2"
shift
;;
-x | --trace)
flags+=(--trace)
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
output_dir="$1"
if [ -d "$output_dir" ]; then
if ! find "$output_dir" -mindepth 1 -exec false {} + 2>/dev/null; then
abort --no-print-usage "Directory '$output_dir' must be empty for --gather-test-outputs-in"
fi
elif ! mkdir "$output_dir" 2>/dev/null; then
abort --no-print-usage "Could not create '$output_dir' for --gather-test-outputs-in"
fi
flags+=(--gather-test-outputs-in "$output_dir")
;;
--setup-suite-file)
shift
setup_suite_file="$1"
;;
--code-quote-style)
shift
BATS_CODE_QUOTE_STYLE="$1"
;;
--filter-status)
shift
flags+=('--filter-status' "$1")
;;
--filter-tags)
shift
flags+=('--filter-tags' "$1")
;;
--line-reference-format)
shift
BATS_LINE_REFERENCE_FORMAT=$1
;;
-*)
abort "Bad command line option '$1'"
;;
*)
arguments+=("$1")
;;
esac
shift
done
if [[ ! $BATS_LINE_REFERENCE_FORMAT =~ (custom|comma_line|colon|uri) ]]; then
abort "Invalid BATS_LINE_REFERENCE_FORMAT '$BATS_LINE_REFERENCE_FORMAT' (e.g. via --line-reference-format)"
fi
if [[ -n "${BATS_RUN_TMPDIR:-}" ]]; then
if [[ -d "$BATS_RUN_TMPDIR" ]]; then
printf "Error: BATS_RUN_TMPDIR (%s) already exists\n" "$BATS_RUN_TMPDIR" >&2
printf "Reusing old run directories can lead to unexpected results ... aborting!\n" >&2
exit 1
elif ! mkdir -p "$BATS_RUN_TMPDIR"; then
printf "Error: Failed to create BATS_RUN_TMPDIR (%s)\n" "$BATS_RUN_TMPDIR" >&2
exit 1
fi
elif ! BATS_RUN_TMPDIR=$(mktemp -d "${BATS_TMPDIR}/bats-run-XXXXXX"); then
printf "Error: Failed to create BATS_RUN_TMPDIR (%s) with mktemp\n" "${BATS_TMPDIR}/bats-run-XXXXXX" >&2
exit 1
fi
export BATS_WARNING_FILE="${BATS_RUN_TMPDIR}/warnings.log"
bats_exit_trap() {
if [[ -s "$BATS_WARNING_FILE" ]]; then
local pre_cat='' post_cat=''
if [[ $formatter == pretty ]]; then
pre_cat=$'\x1B[31m'
post_cat=$'\x1B[0m'
fi
printf "\nThe following warnings were encountered during tests:\n%s" "$pre_cat"
cat "$BATS_WARNING_FILE"
printf "%s" "$post_cat"
fi >&2
if [[ -n "$BATS_TEMPDIR_CLEANUP" ]]; then
rm -rf "$BATS_RUN_TMPDIR"
else
printf "BATS_RUN_TMPDIR: %s\n" "$BATS_RUN_TMPDIR" >&2
fi
}
trap bats_exit_trap EXIT
if [[ "$formatter" != "tap" ]]; then
flags+=('-x')
fi
if [[ -n "$report_formatter" && "$report_formatter" != "tap" ]]; then
flags+=('-x')
fi
if [[ "$formatter" == "junit" ]]; then
flags+=('-T')
formatter_flags+=('--base-path' "${arguments[0]}")
fi
if [[ "$report_formatter" == "junit" ]]; then
flags+=('-T')
report_formatter_flags+=('--base-path' "${arguments[0]}")
fi
if [[ "$formatter" == "pretty" ]]; then
formatter_flags+=('--base-path' "${arguments[0]}")
fi
# if we don't need to filter extended syntax, use the faster formatter
if [[ "$formatter" == tap && -z "$report_formatter" ]]; then
formatter="cat"
fi
bats_check_formatter() { # <formatter-path>
local -r formatter="$1"
if [[ ! -f "$formatter" ]]; then
printf "ERROR: Formatter '%s' is not readable!\n" "$formatter"
exit 1
elif [[ ! -x "$formatter" ]]; then
printf "ERROR: Formatter '%s' is not executable!\n" "$formatter"
exit 1
fi
}
if [[ $formatter == /* ]]; then # absolute paths are direct references to formatters
bats_check_formatter "$formatter"
interpolated_formatter="$formatter"
else
interpolated_formatter="bats-format-${formatter}"
fi
if [[ "${#arguments[@]}" -eq 0 ]]; then
abort 'Must specify at least one <test>'
fi
if [[ -n "$report_formatter" ]]; then
if [[ ! -w "${BATS_REPORT_OUTPUT_DIR}" ]]; then
abort "Output path ${BATS_REPORT_OUTPUT_DIR} is not writeable"
fi
# only set BATS_REPORT_FILENAME if none was given
if [[ -z "${BATS_REPORT_FILENAME:-}" ]]; then
case "$report_formatter" in
tap | tap13)
BATS_REPORT_FILENAME="report.tap"
;;
junit)
BATS_REPORT_FILENAME="report.xml"
;;
*)
BATS_REPORT_FILENAME="report.log"
;;
esac
fi
fi
if [[ $report_formatter == /* ]]; then # absolute paths are direct references to formatters
bats_check_formatter "$report_formatter"
interpolated_report_formatter="${report_formatter}"
else
interpolated_report_formatter="bats-format-${report_formatter}"
fi
if [[ "${BATS_CODE_QUOTE_STYLE-BATS_CODE_QUOTE_STYLE_UNSET}" == BATS_CODE_QUOTE_STYLE_UNSET ]]; then
BATS_CODE_QUOTE_STYLE="\`'"
fi
case "${BATS_CODE_QUOTE_STYLE}" in
??)
BATS_BEGIN_CODE_QUOTE="${BATS_CODE_QUOTE_STYLE::1}"
BATS_END_CODE_QUOTE="${BATS_CODE_QUOTE_STYLE:1:1}"
export BATS_BEGIN_CODE_QUOTE BATS_END_CODE_QUOTE
;;
custom)
if [[ ${BATS_BEGIN_CODE_QUOTE-BATS_BEGIN_CODE_QUOTE_UNSET} == BATS_BEGIN_CODE_QUOTE_UNSET ||
${BATS_END_CODE_QUOTE-BATS_BEGIN_CODE_QUOTE_UNSET} == BATS_BEGIN_CODE_QUOTE_UNSET ]]; then
printf "ERROR: BATS_CODE_QUOTE_STYLE=custom requires BATS_BEGIN_CODE_QUOTE and BATS_END_CODE_QUOTE to be set\n" >&2
exit 1
fi
;;
*)
printf "ERROR: Unknown BATS_CODE_QUOTE_STYLE: %s\n" "$BATS_CODE_QUOTE_STYLE" >&2
exit 1
;;
esac
if [[ -n "$setup_suite_file" && ! -f "$setup_suite_file" ]]; then
abort "--setup-suite-file $setup_suite_file does not exist!"
fi
filenames=()
for filename in "${arguments[@]}"; do
expand_path "$filename" 'filename'
if [[ -z "$setup_suite_file" ]]; then
if [[ -d "$filename" ]]; then
dirname="$filename"
else
dirname="${filename%/*}"
fi
potential_setup_suite_file="$dirname/setup_suite.bash"
if [[ -e "$potential_setup_suite_file" ]]; then
setup_suite_file="$potential_setup_suite_file"
fi
fi
if [[ -d "$filename" ]]; then
shopt -s nullglob
if [[ "$recursive" -eq 1 ]]; then
while IFS= read -r -d $'\0' file; do
filenames+=("$file")
done < <(find -L "$filename" -type f -name "*.${BATS_FILE_EXTENSION:-bats}" -print0 | sort -z)
else
for suite_filename in "$filename"/*."${BATS_FILE_EXTENSION:-bats}"; do
filenames+=("$suite_filename")
done
fi
shopt -u nullglob
else
filenames+=("$filename")
fi
done
if [[ -n "$setup_suite_file" ]]; then
flags+=("--setup-suite-file" "$setup_suite_file")
fi
# shellcheck source=lib/bats-core/validator.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/validator.bash"
trap 'BATS_INTERRUPTED=true' INT # let the lower levels handle the interruption
set -o pipefail execfail
# pipe stdin into command and to stdout
# pipe command stdout to file
bats_tee() { # <output-file> <command...>
local output_file=$1 status=0
shift
exec 3<&1 # use FD3 to get around pipe
tee >(cat >&3) | "$@" >"$output_file" || status=$?
if (( status != 0 )); then
printf "ERROR: command \`%s\` failed with status %d\n" "$*" "$status" >&2
fi
return $status
}
if [[ -n "$report_formatter" ]]; then
exec bats-exec-suite "${flags[@]}" "${filenames[@]}" |
bats_tee "${BATS_REPORT_OUTPUT_DIR}/${BATS_REPORT_FILENAME}" "$interpolated_report_formatter" "${report_formatter_flags[@]}" |
bats_test_count_validator |
"$interpolated_formatter" "${formatter_flags[@]}"
else
exec bats-exec-suite "${flags[@]}" "${filenames[@]}" |
bats_test_count_validator |
"$interpolated_formatter" "${formatter_flags[@]}"
fi
+389
View File
@@ -0,0 +1,389 @@
#!/usr/bin/env bash
set -eET
flags=('--dummy-flag')
num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS:-1}
extended_syntax=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
declare -r BATS_RETRY_RETURN_CODE=126
export BATS_TEST_RETRIES=0 # no retries by default
while [[ "$#" -ne 0 ]]; do
case "$1" in
-j)
shift
num_jobs="$1"
;;
-T)
flags+=('-T')
;;
-x)
flags+=('-x')
extended_syntax=1
;;
--no-parallelize-within-files)
# use singular to allow for users to override in file
BATS_NO_PARALLELIZE_WITHIN_FILE=1
;;
--dummy-flag) ;;
--trace)
flags+=('--trace')
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
flags+=(--gather-test-outputs-in "$1")
;;
*)
break
;;
esac
shift
done
filename="$1"
TESTS_FILE="$2"
if [[ ! -f "$filename" ]]; then
printf 'Testfile "%s" not found\n' "$filename" >&2
exit 1
fi
export BATS_TEST_FILENAME="$filename"
# shellcheck source=lib/bats-core/preprocessing.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/preprocessing.bash"
bats_run_setup_file() {
# shellcheck source=lib/bats-core/tracing.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/tracing.bash"
# shellcheck source=lib/bats-core/test_functions.bash
# 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
# shellcheck disable=2034
BATS_TEST_NAME= # used in tracing.bash
# shellcheck disable=2034
BATS_TEST_COMPLETED= # used in tracing.bash
BATS_SOURCE_FILE_COMPLETED=
BATS_SETUP_FILE_COMPLETED=
BATS_TEARDOWN_FILE_COMPLETED=
# shellcheck disable=2034
BATS_ERROR_STATUS= # used in tracing.bash
touch "$BATS_OUT"
bats_setup_tracing
trap 'bats_file_teardown_trap' EXIT
local status=0
# get the setup_file/teardown_file functions for this file (if it has them)
# shellcheck disable=SC1090
source "$BATS_TEST_SOURCE"
BATS_SOURCE_FILE_COMPLETED=1
bats_set_stacktrace_limit
setup_file >>"$BATS_OUT" 2>&1
BATS_SETUP_FILE_COMPLETED=1
}
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=$?
if ((bats_teardown_file_status == 0)); then
BATS_TEARDOWN_FILE_COMPLETED=1
elif [[ -n "${BATS_SETUP_FILE_COMPLETED:-}" ]]; then
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
BATS_ERROR_STATUS=$bats_teardown_file_status
return $BATS_ERROR_STATUS
fi
}
# shellcheck disable=SC2317
bats_file_teardown_trap() {
bats_run_teardown_file
bats_file_exit_trap in-teardown_trap
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash"
# shellcheck disable=SC2317
bats_file_exit_trap() {
local -r last_return_code=$?
if [[ ${1:-} != in-teardown_trap ]]; then
BATS_ERROR_STATUS=$last_return_code
fi
trap - ERR EXIT
local failure_reason
local -i failure_test_index=$((BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE + 1))
if [[ -n "${BATS_TEST_SKIPPED-}" ]]; then
export BATS_TEST_SKIPPED # indicate to exec-test that it should skip
# print skip message for each test (use this to avoid reimplementing filtering)
bats_run_tests 1<&3 # restore original stdout (this is running in setup_file's redirection to BATS_OUT)
bats_exec_file_status=0 # this should not lead to errors
elif [[ -z "$BATS_SETUP_FILE_COMPLETED" || -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
if [[ -z "$BATS_SETUP_FILE_COMPLETED" ]]; then
failure_reason='setup_file'
elif [[ -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
failure_reason='teardown_file'
failure_test_index=$((BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE + ${#tests_to_run[@]} + 1))
elif [[ -z "$BATS_SOURCE_FILE_COMPLETED" ]]; then
failure_reason='source'
else
failure_reason='unknown internal'
fi
printf "not ok %d %s\n" "$failure_test_index" "$failure_reason failed" >&3
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}" >&3
bats_print_failed_command "${stack_trace[@]}" >&3
bats_prefix_lines_for_tap_output <"$BATS_OUT" | bats_replace_filename >&3
rm -rf "$BATS_OUT"
bats_exec_file_status=1
fi
# setup_file not executed but defined in this test file? -> might be defined in the wrong file
if [[ -z "${BATS_SETUP_SUITE_COMPLETED-}" ]] && declare -F setup_suite >/dev/null; then
bats_generate_warning 3 --no-stacktrace "$BATS_TEST_FILENAME"
fi
exit "$bats_exec_file_status"
}
function setup_file() {
return 0
}
function teardown_file() {
return 0
}
bats_forward_output_of_parallel_test() {
local test_number_in_suite=$1
local status=0
wait "$(cat "$output_folder/$test_number_in_suite/pid")" || status=1
cat "$output_folder/$test_number_in_suite/stdout"
cat "$output_folder/$test_number_in_suite/stderr" >&2
return $status
}
bats_is_next_parallel_test_finished() {
local PID
# get the pid of the next potentially finished test
PID=$(cat "$output_folder/$((test_number_in_suite_of_last_finished_test + 1))/pid")
# try to send a signal to this process
# if it fails, the process exited,
# if it succeeds, the process is still running
if kill -0 "$PID" 2>/dev/null; then
return 1
fi
}
# prints output from all tests in the order they were started
# $1 == "blocking": wait for a test to finish before printing
# != "blocking": abort printing, when a test has not finished
bats_forward_output_for_parallel_tests() {
local status=0
# was the next test already started?
while ((test_number_in_suite_of_last_finished_test + 1 <= test_number_in_suite)); do
# if we are okay with waiting or if the test has already been finished
if [[ "$1" == "blocking" ]] || bats_is_next_parallel_test_finished; then
((++test_number_in_suite_of_last_finished_test))
bats_forward_output_of_parallel_test "$test_number_in_suite_of_last_finished_test" || status=$?
else
# non-blocking and the process has not finished -> abort the printing
break
fi
done
return $status
}
bats_run_test_with_retries() { # <args>
local status=0
local should_try_again=1 try_number
for ((try_number = 1; should_try_again; ++try_number)); do
if "$BATS_LIBEXEC/bats-exec-test" "$@" "$try_number"; then
should_try_again=0
else
status=$?
if ((status == BATS_RETRY_RETURN_CODE)); then
should_try_again=1
status=0 # this is not the last try -> reset status
else
should_try_again=0
bats_exec_file_status=$status
fi
fi
done
return $status
}
bats_run_tests_in_parallel() {
local output_folder="$BATS_RUN_TMPDIR/parallel_output"
local status=0
mkdir -p "$output_folder"
# shellcheck source=lib/bats-core/semaphore.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/semaphore.bash"
bats_semaphore_setup
# the test_number_in_file is not yet incremented -> one before the next test to run
local test_number_in_suite_of_last_finished_test="$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE" # stores which test was printed last
local test_number_in_file=0 test_number_in_suite=$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE
for test_name in "${tests_to_run[@]}"; do
# Only handle non-empty lines
if [[ $test_name ]]; then
((++test_number_in_suite))
((++test_number_in_file))
mkdir -p "$output_folder/$test_number_in_suite"
bats_semaphore_run "$output_folder/$test_number_in_suite" \
bats_run_test_with_retries "${flags[@]}" "$filename" "$test_name" "$test_number_in_suite" "$test_number_in_file" \
>"$output_folder/$test_number_in_suite/pid"
fi
# print results early to get interactive feedback
bats_forward_output_for_parallel_tests non-blocking || status=1 # ignore if we did not finish yet
done
bats_forward_output_for_parallel_tests blocking || status=1
return $status
}
bats_read_tests_list_file() {
local line_number=0
tests_to_run=()
# the global test number must be visible to traps -> not local
local test_number_in_suite=''
while read -r test_line; do
# check if the line begins with filename
# filename might contain some hard to parse characters,
# use simple string operations to work around that issue
if [[ "$filename" == "${test_line::${#filename}}" ]]; then
# get the rest of the line without the separator \t
test_name=${test_line:$((1 + ${#filename}))}
tests_to_run+=("$test_name")
# save the first test's number for later iteration
# this assumes that tests for a file are stored consecutive in the file!
if [[ -z "$test_number_in_suite" ]]; then
test_number_in_suite=$line_number
fi
fi
((++line_number))
done <"$TESTS_FILE"
BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE="$test_number_in_suite"
declare -ri BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE # mark readonly (cannot merge assignment, because value would be lost)
}
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() {
printf "ok %d %s # skip %s\n" "$test_number_in_suite" "$1" "$BATS_RUN_TESTS_SKIPPED_REASON" >&3
return 1
}
local test_number_in_suite=0
for test_name in "${tests_to_run[@]}"; do
((++test_number_in_suite))
eval "$test_name" || true
done
exit 0
fi
if [[ "$num_jobs" -lt 1 ]]; then
printf 'Invalid number of jobs: %s\n' "$num_jobs" >&2
exit 1
fi
if [[ "$num_jobs" != 1 && "${BATS_NO_PARALLELIZE_WITHIN_FILE-False}" == False ]]; then
export BATS_SEMAPHORE_NUMBER_OF_SLOTS="$num_jobs"
bats_run_tests_in_parallel "$BATS_RUN_TMPDIR/parallel_output" || bats_exec_file_status=1
else
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
fi
# Only handle non-empty lines
if [[ -n ${test_name-} ]]; then
((++test_number_in_suite))
((++test_number_in_file))
bats_run_test_with_retries "${flags[@]}" "$filename" "$test_name" \
"$test_number_in_suite" "$test_number_in_file" || bats_exec_file_status=$?
fi
done
fi
}
bats_create_file_tempdirs() {
local bats_files_tmpdir="${BATS_RUN_TMPDIR}/file"
if ! mkdir -p "$bats_files_tmpdir"; then
printf 'Failed to create %s\n' "$bats_files_tmpdir" >&2
exit 1
fi
BATS_FILE_TMPDIR="$bats_files_tmpdir/${BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE?}"
if ! mkdir "$BATS_FILE_TMPDIR"; then
printf 'Failed to create BATS_FILE_TMPDIR=%s\n' "$BATS_FILE_TMPDIR" >&2
exit 1
fi
ln -s "$BATS_TEST_FILENAME" "$BATS_FILE_TMPDIR-$(basename "$BATS_TEST_FILENAME").source_file"
export BATS_FILE_TMPDIR
}
trap 'BATS_INTERRUPTED=true' INT
BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE=0 # predeclare as Bash 3.2 does not support declare -g
bats_read_tests_list_file
# don't run potentially expensive setup/teardown_file
# when there are no tests to run
if [[ ${#tests_to_run[@]} -eq 0 ]]; then
exit 0
fi
if [[ -n "$extended_syntax" ]]; then
printf "suite %s\n" "$filename"
fi
# requires the test list to be read but not empty
bats_create_file_tempdirs
bats_preprocess_source # uses BATS_TEST_FILENAME
trap bats_interrupt_trap INT
bats_run_setup_file
# during tests, we don't want to get backtraces from this level
# just wait for the test to be interrupted and display their trace
trap 'BATS_INTERRUPTED=true' INT
bats_run_tests
trap bats_interrupt_trap INT
bats_run_teardown_file
exit $bats_exec_file_status
+309
View File
@@ -0,0 +1,309 @@
#!/usr/bin/env bash
set -e
count_only_flag=''
filter=''
num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS:-1}
parallel_binary_name=${BATS_PARALLEL_BINARY_NAME:-"parallel"}
bats_no_parallelize_across_files=${BATS_NO_PARALLELIZE_ACROSS_FILES-}
bats_no_parallelize_within_files=
filter_status=''
gather_tests_flags=('--dummy-flag')
flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
setup_suite_file=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS=
abort() {
printf 'Error: %s\n' "$1" >&2
exit 1
}
# shellcheck source=lib/bats-core/common.bash disable=SC2153
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash"
while [[ "$#" -ne 0 ]]; do
case "$1" in
-c)
count_only_flag=1
;;
-f)
shift
filter="$1"
;;
-j)
shift
num_jobs="$1"
flags+=('-j' "$num_jobs")
;;
--parallel-binary-name)
shift
parallel_binary_name="$1"
;;
-T)
flags+=('-T')
;;
-x)
flags+=('-x')
;;
--no-parallelize-across-files)
bats_no_parallelize_across_files=1
;;
--no-parallelize-within-files)
bats_no_parallelize_within_files=1
flags+=("--no-parallelize-within-files")
;;
--filter-status)
shift
filter_status="$1"
;;
--filter-tags)
shift
gather_tests_flags+=("--filter-tags" "$1")
;;
--dummy-flag) ;;
--trace)
flags+=('--trace')
((++BATS_TRACE_LEVEL)) # avoid returning 0
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS=1
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
flags+=(--gather-test-outputs-in "$1")
;;
--setup-suite-file)
shift
setup_suite_file="$1"
;;
*)
break
;;
esac
shift
done
if [[ "$num_jobs" != 1 ]]; then
if ! type -p "${parallel_binary_name}" >/dev/null && "${parallel_binary_name}" --version &>/dev/null && [[ -z "$bats_no_parallelize_across_files" ]]; then
abort "Cannot execute \"${num_jobs}\" jobs without GNU parallel"
fi
# shellcheck source=lib/bats-core/semaphore.bash
source "${BATS_ROOT}/$BATS_LIBDIR/bats-core/semaphore.bash"
bats_semaphore_setup
fi
# create a file that contains all (filtered) tests to run from all files
TESTS_LIST_FILE="${BATS_RUN_TMPDIR}/test_list_file.txt"
TEST_ROOT=${1-}
TEST_ROOT=${TEST_ROOT%/*}
export BATS_RUN_LOGS_DIRECTORY="$TEST_ROOT/.bats/run-logs"
if [[ ! -d "$BATS_RUN_LOGS_DIRECTORY" ]]; then
if [[ -n "$filter_status" ]]; then
printf "Error: --filter-status needs '%s/' to save failed tests. Please create this folder, add it to .gitignore and try again.\n" "$BATS_RUN_LOGS_DIRECTORY"
exit 1
else
BATS_RUN_LOGS_DIRECTORY=
fi
# discard via sink instead of having a conditional later
export BATS_RUNLOG_FILE='/dev/null'
else
# use UTC (-u) to avoid problems with TZ changes
BATS_RUNLOG_DATE=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
export BATS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/${BATS_RUNLOG_DATE}.log" BATS_RUNLOG_DATE
if [[ ! -w "$BATS_RUN_LOGS_DIRECTORY" ]]; then
printf "WARNING: Cannot write in %s. This run will not write a log!\n" "$BATS_RUN_LOGS_DIRECTORY" >&2
BATS_RUNLOG_FILE='/dev/null' # disable runlog file
fi
fi
# create file even if no tests are found so the `read` below won't fail
touch "$TESTS_LIST_FILE"
gather_tests_output=$(TESTS_LIST_FILE="$TESTS_LIST_FILE" filter="$filter" filter_status="$filter_status" bats-gather-tests "${gather_tests_flags[@]}" -- "$@")
test_count=$(grep -c '' "$TESTS_LIST_FILE" || true) # don't fail here when there are no tests
if [[ $gather_tests_output == focus_mode ]]; then
focus_mode=1
else
focus_mode=
fi
if [[ -n "$count_only_flag" ]]; then
printf '%d\n' "${test_count}"
# we don't want to pollute the runlog files, as no tests would have been run
[[ $BATS_RUNLOG_FILE != /dev/null ]] && rm "$BATS_RUNLOG_FILE"
exit 0
fi
if [[ -n "$bats_no_parallelize_across_files" ]] && [[ ! "$num_jobs" -gt 1 ]]; then
abort "The flag --no-parallelize-across-files requires at least --jobs 2"
fi
if [[ -n "$bats_no_parallelize_within_files" ]] && [[ ! "$num_jobs" -gt 1 ]]; then
abort "The flag --no-parallelize-across-files requires at least --jobs 2"
fi
# only abort on the lowest levels
trap 'BATS_INTERRUPTED=true' INT
if [[ -n "$focus_mode" ]]; then
printf "WARNING: This test run only contains tests tagged \`bats:focus\`!\n"
fi
bats_exec_suite_status=0
printf '1..%d\n' "${test_count}"
# No point on continuing if there's no tests.
if [[ "${test_count}" == 0 ]]; then
exit
fi
export BATS_SUITE_TMPDIR="${BATS_RUN_TMPDIR}/suite"
if ! mkdir "$BATS_SUITE_TMPDIR"; then
printf '%s\n' "Failed to create BATS_SUITE_TMPDIR" >&2
exit 1
fi
# Deduplicate filenames (without reordering) to avoid running duplicate tests n by n times.
# (see https://github.com/bats-core/bats-core/issues/329)
# If a file was specified multiple times, we already got it repeatedly in our TESTS_LIST_FILE.
# Thus, it suffices to bats-exec-file it once to run all repeated tests on it.
IFS=$'\n' read -d '' -r -a BATS_UNIQUE_TEST_FILENAMES < <(printf "%s\n" "$@" | nl | sort -k 2 | uniq -f 1 | sort -n | cut -f 2-) || true
# shellcheck source=lib/bats-core/tracing.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/tracing.bash"
bats_setup_tracing
trap bats_suite_exit_trap EXIT
exec 3<&1
# shellcheck disable=SC2317
bats_suite_exit_trap() {
local print_bats_out="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS}"
if [[ -z "${BATS_SETUP_SUITE_COMPLETED}" || -z "${BATS_TEARDOWN_SUITE_COMPLETED}" ]]; then
if [[ -z "${BATS_SETUP_SUITE_COMPLETED}" ]]; then
printf "not ok 1 setup_suite\n"
elif [[ -z "${BATS_TEARDOWN_SUITE_COMPLETED}" ]]; then
printf "not ok %d teardown_suite\n" $((test_count + 1))
fi
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}"
bats_print_failed_command "${stack_trace[@]}"
print_bats_out=1
bats_exec_suite_status=1
fi
if [[ -n "$print_bats_out" ]]; then
bats_prefix_lines_for_tap_output <"$BATS_OUT"
fi
if [[ ${BATS_INTERRUPTED-NOTSET} != NOTSET ]]; then
printf "\n# Received SIGINT, aborting ...\n\n"
fi
if [[ -d "$BATS_RUN_LOGS_DIRECTORY" && -n "${BATS_INTERRUPTED:-}" ]]; then
# aborting a test run with CTRL+C does not save the runlog file
[[ "$BATS_RUNLOG_FILE" != /dev/null ]] && rm "$BATS_RUNLOG_FILE"
fi
exit "$bats_exec_suite_status"
} >&3
bats_run_teardown_suite() {
local bats_teardown_suite_status=0
# avoid being called twice, in case this is not called through bats_teardown_suite_trap
# but from the end of file
trap bats_suite_exit_trap EXIT
bats_set_stacktrace_limit
BATS_TEARDOWN_SUITE_COMPLETED=
teardown_suite >>"$BATS_OUT" 2>&1 || bats_teardown_suite_status=$?
if ((bats_teardown_suite_status == 0)); then
BATS_TEARDOWN_SUITE_COMPLETED=1
elif [[ -n "${BATS_SETUP_SUITE_COMPLETED:-}" ]]; then
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
BATS_ERROR_STATUS=$bats_teardown_suite_status
return $BATS_ERROR_STATUS
fi
}
# shellcheck disable=SC2317
bats_teardown_suite_trap() {
bats_run_teardown_suite
bats_suite_exit_trap
}
teardown_suite() {
:
}
trap bats_teardown_suite_trap EXIT
BATS_OUT="$BATS_RUN_TMPDIR/suite.out"
if [[ -n "$setup_suite_file" ]]; then
setup_suite() {
printf "%s does not define \`setup_suite()\`\n" "$setup_suite_file" >&2
return 1
}
# shellcheck disable=SC2034 # will be used in the sourced file below
BATS_TEST_FILENAME="$setup_suite_file"
# shellcheck source=lib/bats-core/test_functions.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/test_functions.bash"
_bats_test_functions_setup -1 # invalid TEST_NUMBER, as this is not a test
# shellcheck disable=SC1090
source "$setup_suite_file"
bats_set_stacktrace_limit
set -eET
export BATS_SETUP_SUITE_COMPLETED=
setup_suite >>"$BATS_OUT" 2>&1
BATS_SETUP_SUITE_COMPLETED=1
set +ET
else
# prevent exit trap from printing an error because of incomplete setup_suite,
# when there was none to execute
BATS_SETUP_SUITE_COMPLETED=1
fi
if [[ "$num_jobs" -gt 1 ]] && [[ -z "$bats_no_parallelize_across_files" ]]; then
# run files in parallel to get the maximum pool of parallel tasks
# shellcheck disable=SC2086,SC2068
# we need to handle the quoting of ${flags[@]} ourselves,
# because parallel can only quote it as one
"${parallel_binary_name}" --keep-order --jobs "$num_jobs" -- "bats-exec-file" "$(printf "%q " "${flags[@]}")" "{}" "$TESTS_LIST_FILE" <<< "$(printf "%s\n" "${BATS_UNIQUE_TEST_FILENAMES[@]}")" 2>&1 || bats_exec_suite_status=1
else
for filename in "${BATS_UNIQUE_TEST_FILENAMES[@]}"; do
if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
bats_exec_suite_status=130 # bash's code for SIGINT exits
break
fi
bats-exec-file "${flags[@]}" "$filename" "${TESTS_LIST_FILE}" || bats_exec_suite_status=1
done
fi
set -eET
bats_run_teardown_suite
if [[ "$focus_mode" == 1 && $bats_exec_suite_status -eq 0 ]]; then
if [[ ${BATS_NO_FAIL_FOCUS_RUN-} == 1 ]]; then
printf "WARNING: This test run only contains tests tagged \`bats:focus\`!\n"
else
printf "Marking test run as failed due to \`bats:focus\` tag. (Set \`BATS_NO_FAIL_FOCUS_RUN=1\` to disable.)\n" >&2
bats_exec_suite_status=1
fi
fi
exit "$bats_exec_suite_status" # the actual exit code will be set by the exit trap using bats_exec_suite_status
+377
View File
@@ -0,0 +1,377 @@
#!/usr/bin/env bash
set -eET
# Variables used in other scripts.
BATS_ENABLE_TIMING=''
BATS_EXTENDED_SYNTAX=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
BATS_PRINT_OUTPUT_ON_FAILURE="${BATS_PRINT_OUTPUT_ON_FAILURE:-}"
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS:-}"
BATS_VERBOSE_RUN="${BATS_VERBOSE_RUN:-}"
BATS_GATHER_TEST_OUTPUTS_IN="${BATS_GATHER_TEST_OUTPUTS_IN:-}"
BATS_TEST_NAME_PREFIX="${BATS_TEST_NAME_PREFIX:-}"
while [[ "$#" -ne 0 ]]; do
case "$1" in
-T)
BATS_ENABLE_TIMING='-T'
;;
-x)
# shellcheck disable=SC2034
BATS_EXTENDED_SYNTAX='-x'
;;
--dummy-flag) ;;
--trace)
((++BATS_TRACE_LEVEL)) # avoid returning 0
;;
--print-output-on-failure)
BATS_PRINT_OUTPUT_ON_FAILURE=1
;;
--show-output-of-passing-tests)
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS=1
;;
--verbose-run)
BATS_VERBOSE_RUN=1
;;
--gather-test-outputs-in)
shift
BATS_GATHER_TEST_OUTPUTS_IN="$1"
;;
*)
break
;;
esac
shift
done
export BATS_TEST_FILENAME="$1"
export BATS_TEST_NAME="$2"
export BATS_SUITE_TEST_NUMBER="$3"
export BATS_TEST_NUMBER="$4"
BATS_TEST_TRY_NUMBER="$5"
if [[ -z "$BATS_TEST_FILENAME" ]]; then
printf 'usage: bats-exec-test <filename>\n' >&2
exit 1
elif [[ ! -f "$BATS_TEST_FILENAME" ]]; then
printf 'bats: %s does not exist\n' "$BATS_TEST_FILENAME" >&2
exit 1
fi
bats_create_test_tmpdirs() {
local tests_tmpdir="${BATS_RUN_TMPDIR}/test"
if ! mkdir -p "$tests_tmpdir"; then
printf 'Failed to create: %s\n' "$tests_tmpdir" >&2
exit 1
fi
BATS_TEST_TMPDIR="$tests_tmpdir/$BATS_SUITE_TEST_NUMBER"
if ! mkdir "$BATS_TEST_TMPDIR"; then
printf 'Failed to create BATS_TEST_TMPDIR%d: %s\n' "$BATS_TEST_TRY_NUMBER" "$BATS_TEST_TMPDIR" >&2
exit 1
fi
printf "%s\n" "$BATS_TEST_NAME" >"$BATS_TEST_TMPDIR.name"
export BATS_TEST_TMPDIR
}
# load the test helper functions like `load` or `run` that are needed to run a (preprocessed) .bats file without bash errors
# shellcheck source=lib/bats-core/test_functions.bash disable=SC2153
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/test_functions.bash"
_bats_test_functions_setup "$BATS_TEST_NUMBER"
# shellcheck source=lib/bats-core/tracing.bash disable=SC2153
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/tracing.bash"
bats_teardown_trap() {
bats_check_status_from_trap
local bats_teardown_trap_status=0
# `bats_teardown_trap` is always called with one parameter (BATS_TEARDOWN_STARTED)
# The second parameter is optional and corresponds for to the killer pid
# that will be forwarded to the exit trap to kill it if necessary
local killer_pid=${2:-}
bats_set_stacktrace_limit
# mark the start of this function to distinguish where skip is called
# parameter 1 will signify the reason why this function was called
# this is used to identify when this is called as exit trap function
BATS_TEARDOWN_STARTED=${1:-1}
teardown >>"$BATS_OUT" 2>&1 || bats_teardown_trap_status="$?"
if [[ $bats_teardown_trap_status -eq 0 ]]; then
BATS_TEARDOWN_COMPLETED=1
elif [[ -n "$BATS_TEST_COMPLETED" ]]; then
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
BATS_ERROR_STATUS="$bats_teardown_trap_status"
fi
bats_exit_trap "$killer_pid"
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash"
bats_exit_trap() {
local status
local exit_metadata=''
local killer_pid=${1:-}
trap - ERR EXIT
if [[ -n "${BATS_TEST_TIMEOUT:-}" ]]; then
# Kill the watchdog in the case of of kernel finished before the timeout
bats_abort_timeout_countdown "$killer_pid" || status=1
fi
if [[ -n "$BATS_TEST_SKIPPED" ]]; then
exit_metadata=' # skip'
if [[ "$BATS_TEST_SKIPPED" != '1' ]]; then
exit_metadata+=" $BATS_TEST_SKIPPED"
fi
elif [[ "${BATS_TIMED_OUT-NOTSET}" != NOTSET ]]; then
exit_metadata=" # timeout after ${BATS_TEST_TIMEOUT}s"
fi
BATS_TEST_TIME=''
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
get_mills_since_epoch BATS_TEST_END_TIME
BATS_TEST_TIME=" in "$((BATS_TEST_END_TIME - BATS_TEST_START_TIME))"ms"
fi
local print_bats_out="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS}"
local should_retry=''
if [[ -z "$BATS_TEST_COMPLETED" || -z "$BATS_TEARDOWN_COMPLETED" || "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
if [[ "$BATS_ERROR_STATUS" -eq 0 ]]; then
# For some versions of bash, `$?` may not be set properly for some error
# conditions before triggering the EXIT trap directly (see #72 and #81).
# Thanks to the `BATS_TEARDOWN_COMPLETED` signal, this will pinpoint such
# errors if they happen during `teardown()` when `bats_perform_test` calls
# `bats_teardown_trap` directly after the test itself passes.
#
# If instead the test fails, and the `teardown()` error happens while
# `bats_teardown_trap` runs as the EXIT trap, the test will fail with no
# output, since there's no way to reach the `bats_exit_trap` call.
BATS_ERROR_STATUS=1
fi
if bats_should_retry_test; then
should_retry=1
status=126 # signify retry
rm -r "$BATS_TEST_TMPDIR" # clean up for retry
else
printf 'not ok %d %s%s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" "$exit_metadata" >&3
if (( ${#BATS_TEST_TAGS[@]} > 0 )); then
printf '# tags:'
printf ' %s' "${BATS_TEST_TAGS[@]}"
printf '\n'
fi >&3
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}" >&3
bats_print_failed_command "${stack_trace[@]}" >&3
if [[ $BATS_PRINT_OUTPUT_ON_FAILURE ]]; then
if [[ -n "${output:-}" ]]; then
printf "Last output:\n%s\n" "$output"
fi
if [[ -n "${stderr:-}" ]]; then
printf "Last stderr: \n%s\n" "$stderr"
fi
fi >>"$BATS_OUT"
print_bats_out=1
status=1
local state=failed
fi
else
printf 'ok %d %s%s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" \
"$exit_metadata" >&3
status=0
local state=passed
fi
if [[ -z "$should_retry" ]]; then
printf "%s %s\t%s\n" "$state" "$BATS_TEST_FILENAME" "$BATS_TEST_NAME" >>"$BATS_RUNLOG_FILE"
if [[ $print_bats_out ]]; then
bats_prefix_lines_for_tap_output <"$BATS_OUT" | bats_replace_filename >&3
fi
fi
if [[ $BATS_GATHER_TEST_OUTPUTS_IN ]]; then
local try_suffix=
if [[ -n "$should_retry" ]]; then
try_suffix="-try$BATS_TEST_TRY_NUMBER"
fi
cp "$BATS_OUT" "$BATS_GATHER_TEST_OUTPUTS_IN/$BATS_SUITE_TEST_NUMBER$try_suffix-${BATS_TEST_DESCRIPTION//\//%2F}.log"
fi
rm -f "$BATS_OUT"
exit "$status"
}
# Marks the test as failed due to timeout.
# The actual termination of subprocesses is done via pkill in the background
# process in bats_start_timeout_countdown
# shellcheck disable=SC2317
bats_timeout_trap() {
BATS_TIMED_OUT=1
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=
exit 1
}
bats_get_child_processes_of() { # <parent-pid>
local -ri parent_pid=${1?}
{
read -ra header
local pid_col ppid_col
for ((i = 0; i < ${#header[@]}; ++i)); do
if [[ ${header[$i]} == "PID" ]]; then
pid_col=$i
fi
if [[ ${header[$i]} == "PPID" ]]; then
ppid_col=$i
fi
done
while read -ra row; do
if ((${row[$ppid_col]} == parent_pid)); then
printf "%d\n" "${row[$pid_col]}"
fi
done
} < <(ps -ef "$parent_pid")
}
bats_kill_childprocesses_of() { # <parent-pid>
local -ir parent_pid="${1?}"
if command -v pkill; then
pkill -P "$parent_pid"
else
# kill in reverse order (latest first)
while read -r pid; do
kill "$pid"
done < <(bats_get_child_processes_of "$parent_pid" | sort -r)
fi >/dev/null
}
# sets a timeout for this process
#
# using SIGABRT for interprocess communication.
# Ruled out:
# USR1/2 - not available on Windows
# SIGALRM - interferes with sleep:
# "sleep(3) may be implemented using SIGALRM; mixing calls to alarm()
# and sleep(3) is a bad idea." ~ https://linux.die.net/man/2/alarm
bats_start_timeout_countdown() { # <timeout>
local -ri timeout=$1
local -ri target_pid=$$
# shellcheck disable=SC2064
trap "bats_timeout_trap $target_pid" ABRT
if ! (command -v ps || command -v pkill) >/dev/null; then
printf "Error: Cannot execute timeout because neither pkill nor ps are available on this system!\n" >&2
exit 1
fi
# Start another process to kill the children of this process
(
sleep "$timeout" &
# sleep won't receive signals, so we use wait below
# and kill sleep explicitly when signalled to do so
# shellcheck disable=SC2064
trap "kill $!; exit 0" ABRT
wait
if kill -ABRT "$target_pid"; then
# get rid of signal blocking child processes (like sleep)
bats_kill_childprocesses_of "$target_pid"
fi &>/dev/null
) &
}
bats_abort_timeout_countdown() {
# kill the countdown process, don't care if its still there
kill -ABRT "$1" &>/dev/null || true
}
if [[ -n "${EPOCHREALTIME-}" ]]; then
get_mills_since_epoch() { # <output-variable>
local -r output_variable="$1"
local int frac
# allow for different decimal separators
IFS=., read -r int frac <<<"$EPOCHREALTIME"
printf -v "$output_variable" "%d" "${int}${frac::3}"
}
else
get_mills_since_epoch() { # <output-variable>
local -r output_variable="$1"
local ms_since_epoch
ms_since_epoch=$(bats_execute date +%s%N)
if [[ "$ms_since_epoch" == *N || "${#ms_since_epoch}" -lt 19 ]]; then
ms_since_epoch=$(($(bats_execute date +%s) * 1000))
else
ms_since_epoch=$((ms_since_epoch / 1000000))
fi
printf -v "$output_variable" "%d" "$ms_since_epoch"
}
fi
bats_perform_test() {
if ! declare -F "${BATS_TEST_NAME%% *}" &>/dev/null; then
local quoted_test_name
bats_quote_code quoted_test_name "$BATS_TEST_NAME"
printf "bats: unknown test name %s\n" "$quoted_test_name" >&2
exit 1
fi
# is this skipped from outside ?
if [[ -n "${BATS_TEST_SKIPPED-}" ]]; then
# forward skip (with message) by overriding setup
# shellcheck disable=SC2317
setup() {
skip "$BATS_TEST_SKIPPED"
}
fi
local BATS_killer_pid=''
if [[ -n "${BATS_TEST_TIMEOUT:-}" ]]; then
bats_start_timeout_countdown "$BATS_TEST_TIMEOUT"
BATS_killer_pid=$!
fi
BATS_TEST_COMPLETED=
BATS_TEST_SKIPPED=${BATS_TEST_SKIPPED-}
BATS_TEARDOWN_COMPLETED=
BATS_ERROR_STATUS=
bats_setup_tracing
# use parameter to mark this call as trap call
# shellcheck disable=SC2064
trap "bats_teardown_trap as-exit-trap $BATS_killer_pid" EXIT
if [[ -n "$BATS_EXTENDED_SYNTAX" ]]; then
printf 'begin %d %s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}$BATS_TEST_DESCRIPTION" >&3
fi
get_mills_since_epoch BATS_TEST_START_TIME
{
bats_set_stacktrace_limit
setup "$@"
"$@"
} >>"$BATS_OUT" 2>&1 4>&1
BATS_TEST_COMPLETED=1
# shellcheck disable=SC2064
trap "bats_exit_trap $BATS_killer_pid" EXIT
bats_teardown_trap "" "$BATS_killer_pid" # pass empty parameter to signify call outside trap
}
trap bats_interrupt_trap INT
# shellcheck source=lib/bats-core/preprocessing.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/preprocessing.bash"
exec 3<&1
bats_create_test_tmpdirs
bats_evaluate_preprocessed_source
readonly BATS_TEST_TAGS
# use eval to parse (internally quoted!) test command into parameters
bats_perform_test "${BATS_TEST_COMMAND[@]}"
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -e
trap '' INT
cat
+254
View File
@@ -0,0 +1,254 @@
#!/usr/bin/env bash
set -euo pipefail
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/formatter.bash"
BASE_PATH=.
while [[ "$#" -ne 0 ]]; do
case "$1" in
--base-path)
shift
normalize_base_path BASE_PATH "$1"
;;
esac
shift
done
init_suite() {
suite_test_exec_time=0
# since we have to print the suite header before its contents but we don't know the contents before the header,
# we have to buffer the contents
_suite_buffer=""
test_result_state="" # declare for the first flush, when no test has been encountered
}
_buffer_log=
init_file() {
file_count=0
file_failures=0
file_skipped=0
file_exec_time=0
test_exec_time=0
name=""
_buffer=""
_buffer_log=""
_system_out_log=""
test_result_state="" # mark that no test has run in this file so far
}
host() {
local hostname="${HOST:-}"
[[ -z "$hostname" ]] && hostname="${HOSTNAME:-}"
[[ -z "$hostname" ]] && hostname="$(uname -n)"
[[ -z "$hostname" ]] && hostname="$(hostname -f)"
echo "$hostname"
}
# convert $1 (time in milliseconds) to seconds
milliseconds_to_seconds() {
# we cannot rely on having bc for this calculation
full_seconds=$(($1 / 1000))
remaining_milliseconds=$(($1 % 1000))
if [[ $remaining_milliseconds -eq 0 ]]; then
printf "%d" "$full_seconds"
else
printf "%d.%03d" "$full_seconds" "$remaining_milliseconds"
fi
}
suite_header() {
printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<testsuites time=\"%s\">\n" "$(milliseconds_to_seconds "${suite_test_exec_time}")"
}
file_header() {
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S")
printf "<testsuite name=\"%s\" tests=\"%s\" failures=\"%s\" errors=\"0\" skipped=\"%s\" time=\"%s\" timestamp=\"%s\" hostname=\"%s\">\n" \
"$(xml_escape "${class}")" "${file_count}" "${file_failures}" "${file_skipped}" "$(milliseconds_to_seconds "${file_exec_time}")" "${timestamp}" "$(host)"
}
file_footer() {
printf "</testsuite>\n"
}
suite_footer() {
printf "</testsuites>\n"
}
print_test_case() {
if [[ "$test_result_state" == ok && -z "$_system_out_log" && -z "$_buffer_log" ]]; then
# pass and no output can be shortened
printf " <testcase classname=\"%s\" name=\"%s\" time=\"%s\" />\n" "$(xml_escape "${class}")" "$(xml_escape "${name}")" "$(milliseconds_to_seconds "${test_exec_time}")"
else
printf " <testcase classname=\"%s\" name=\"%s\" time=\"%s\">\n" "$(xml_escape "${class}")" "$(xml_escape "${name}")" "$(milliseconds_to_seconds "${test_exec_time}")"
if [[ -n "$_system_out_log" ]]; then
printf " <system-out>%s</system-out>\n" "$(xml_escape "${_system_out_log}")"
fi
if [[ -n "$_buffer_log" || "$test_result_state" == not_ok ]]; then
printf " <failure type=\"failure\">%s</failure>\n" "$(xml_escape "${_buffer_log}")"
fi
if [[ "$test_result_state" == skipped ]]; then
printf " <skipped>%s</skipped>\n" "$(xml_escape "$test_skip_message")"
fi
printf " </testcase>\n"
fi
}
xml_escape() {
output=${1//&/\&amp;}
output=${output//</\&lt;}
output=${output//>/\&gt;}
output=${output//'"'/\&quot;}
output=${output//\'/\&#39;}
# remove ANSI Color codes
local CONTROL_CHAR=$'\033'
local REGEX="$CONTROL_CHAR\[[0-9;]+m"
while [[ "$output" =~ $REGEX ]]; do
output=${output//${BASH_REMATCH[0]}/}
done
printf "%s" "$output"
}
suite_buffer() {
local output
output="$(
"$@"
printf "x"
)" # use x marker to avoid losing trailing newlines
_suite_buffer="${_suite_buffer}${output%x}"
}
suite_flush() {
echo -n "${_suite_buffer}"
_suite_buffer=""
}
buffer() {
local output
output="$(
"$@"
printf "x"
)" # use x marker to avoid losing trailing newlines
_buffer="${_buffer}${output%x}"
}
flush() {
echo -n "${_buffer}"
_buffer=""
}
log() {
if [[ -n "$_buffer_log" ]]; then
_buffer_log="${_buffer_log}
$1"
else
_buffer_log="$1"
fi
}
flush_log() {
if [[ -n "$test_result_state" ]]; then
buffer print_test_case
fi
_buffer_log=""
_system_out_log=""
}
log_system_out() {
if [[ -n "$_system_out_log" ]]; then
_system_out_log="${_system_out_log}
$1"
else
_system_out_log="$1"
fi
}
finish_file() {
if [[ "${class-JUNIT_FORMATTER_NO_FILE_ENCOUNTERED}" != JUNIT_FORMATTER_NO_FILE_ENCOUNTERED ]]; then
file_header
printf "%s\n" "${_buffer}"
file_footer
fi
}
finish_suite() {
flush_log
suite_header
suite_flush
finish_file # must come after suite flush to not print the last file before the others
suite_footer
}
bats_tap_stream_plan() { # <number of tests>
:
}
init_suite
trap finish_suite EXIT
trap '' INT
bats_tap_stream_begin() { # <test index> <test name>
flush_log
# set after flushing to avoid overriding name of test
name="$2"
}
bats_tap_stream_ok() { # <test index> <test name>
test_exec_time=${BATS_FORMATTER_TEST_DURATION:-0}
((file_count += 1))
test_result_state='ok'
file_exec_time="$((file_exec_time + test_exec_time))"
suite_test_exec_time=$((suite_test_exec_time + test_exec_time))
}
bats_tap_stream_skipped() { # <test index> <test name> <skip reason>
test_exec_time=${BATS_FORMATTER_TEST_DURATION:-0}
((file_count += 1))
((file_skipped += 1))
test_result_state='skipped'
test_exec_time=0
test_skip_message="$3"
}
bats_tap_stream_not_ok() { # <test index> <test name>
test_exec_time=${BATS_FORMATTER_TEST_DURATION:-0}
((file_count += 1))
((file_failures += 1))
test_result_state=not_ok
file_exec_time="$((file_exec_time + test_exec_time))"
suite_test_exec_time=$((suite_test_exec_time + test_exec_time))
}
bats_tap_stream_comment() { # <comment text without leading '# '> <scope>
local comment="$1" scope="$2"
case "$scope" in
begin)
# everything that happens between begin and [not] ok is FD3 output from the test
log_system_out "$comment"
;;
ok)
# non failed tests can produce FD3 output
log_system_out "$comment"
;;
*)
# everything else is considered error output
log "$1"
;;
esac
}
bats_tap_stream_suite() { # <file name>
flush_log
suite_buffer finish_file
init_file
class="${1/$BASE_PATH/}"
}
bats_tap_stream_unknown() { # <full line>
:
}
bats_parse_internal_extended_tap
+348
View File
@@ -0,0 +1,348 @@
#!/usr/bin/env bash
set -e
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/formatter.bash"
BASE_PATH=.
BATS_ENABLE_TIMING=
while [[ "$#" -ne 0 ]]; do
case "$1" in
-T)
BATS_ENABLE_TIMING="-T"
;;
--base-path)
shift
normalize_base_path BASE_PATH "$1"
;;
esac
shift
done
update_count_column_width() {
count_column_width=$((${#count} * 2 + 2))
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
# additional space for ' in %s sec'
count_column_width=$((count_column_width + ${#SECONDS} + 8))
fi
# also update dependent value
update_count_column_left
}
update_screen_width() {
screen_width="$(tput cols)"
# also update dependent value
update_count_column_left
}
update_count_column_left() {
count_column_left=$((screen_width - count_column_width))
}
# avoid unset variables
count=0
screen_width=80
update_count_column_width
update_screen_width
test_result=
trap update_screen_width WINCH
begin() {
test_result= # reset to avoid carrying over result state from previous test
line_backoff_count=0
go_to_column 0
update_count_column_width
buffer_with_truncation $((count_column_left - 1)) ' %s' "$name"
clear_to_end_of_line
go_to_column $count_column_left
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
buffer "%${#count}s/${count} in %s sec" "$index" "$SECONDS"
else
buffer "%${#count}s/${count}" "$index"
fi
go_to_column 1
}
finish_test() {
move_up $line_backoff_count
go_to_column 0
buffer "$@"
if [[ -n "${TIMEOUT-}" ]]; then
set_color 2
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
buffer ' [%s (timeout: %s)]' "$TIMING" "$TIMEOUT"
else
buffer ' [timeout: %s]' "$TIMEOUT"
fi
else
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
set_color 2
buffer ' [%s]' "$TIMING"
fi
fi
advance
move_down $((line_backoff_count - 1))
}
pass() {
local TIMING="${1:-}"
finish_test ' ✓ %s' "$name"
test_result=pass
}
skip() {
local reason="$1" TIMING="${2:-}"
if [[ -n "$reason" ]]; then
reason=": $reason"
fi
finish_test ' - %s (skipped%s)' "$name" "$reason"
test_result=skip
}
fail() {
local TIMING="${1:-}"
set_color 1 bold
finish_test ' ✗ %s' "$name"
test_result=fail
}
timeout() {
local TIMING="${1:-}"
set_color 3 bold
TIMEOUT="${2:-}" finish_test ' ✗ %s' "$name"
test_result=timeout
}
log() {
case ${test_result} in
pass)
clear_color
;;
fail)
set_color 1
;;
timeout)
set_color 3
;;
esac
buffer ' %s\n' "$1"
clear_color
}
summary() {
if [ "$failures" -eq 0 ]; then
set_color 2 bold
else
set_color 1 bold
fi
buffer '\n%d test' "$count"
if [[ "$count" -ne 1 ]]; then
buffer 's'
fi
buffer ', %d failure' "$failures"
if [[ "$failures" -ne 1 ]]; then
buffer 's'
fi
if [[ "$skipped" -gt 0 ]]; then
buffer ', %d skipped' "$skipped"
fi
if ((timed_out > 0)); then
buffer ', %d timed out' "$timed_out"
fi
not_run=$((count - passed - failures - skipped - timed_out))
if [[ "$not_run" -gt 0 ]]; then
buffer ', %d not run' "$not_run"
fi
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
buffer " in $SECONDS seconds"
fi
buffer '\n'
clear_color
}
buffer_with_truncation() {
local width="$1"
shift
local string
# shellcheck disable=SC2059
printf -v 'string' -- "$@"
if [[ "${#string}" -gt "$width" ]]; then
buffer '%s...' "${string:0:$((width - 4))}"
else
buffer '%s' "$string"
fi
}
move_up() {
if [[ $1 -gt 0 ]]; then # avoid moving if we got 0
buffer '\x1B[%dA' "$1"
fi
}
move_down() {
if [[ $1 -gt 0 ]]; then # avoid moving if we got 0
buffer '\x1B[%dB' "$1"
fi
}
go_to_column() {
local column="$1"
buffer '\x1B[%dG' $((column + 1))
}
clear_to_end_of_line() {
buffer '\x1B[K'
}
advance() {
clear_to_end_of_line
buffer '\n'
clear_color
}
set_color() {
local color="$1"
local weight=22
if [[ "${2:-}" == 'bold' ]]; then
weight=1
fi
buffer '\x1B[%d;%dm' "$((30 + color))" "$weight"
}
clear_color() {
buffer '\x1B[0m'
}
_buffer=
buffer() {
local content
# shellcheck disable=SC2059
printf -v content -- "$@"
_buffer+="$content"
}
prefix_buffer_with() {
local old_buffer="$_buffer"
_buffer=''
"$@"
_buffer="$_buffer$old_buffer"
}
flush() {
printf '%s' "$_buffer"
_buffer=
}
finish() {
flush
printf '\n'
}
trap finish EXIT
trap '' INT
bats_tap_stream_plan() {
count="$1"
index=0
passed=0
failures=0
skipped=0
timed_out=0
name=
update_count_column_width
}
bats_tap_stream_begin() {
index="$1"
name="$2"
begin
flush
}
bats_tap_stream_ok() {
index="$1"
name="$2"
((++passed))
pass "${BATS_FORMATTER_TEST_DURATION:-}"
}
bats_tap_stream_skipped() {
index="$1"
name="$2"
((++skipped))
skip "$3" "${BATS_FORMATTER_TEST_DURATION:-}"
}
bats_tap_stream_not_ok() {
index="$1"
name="$2"
if [[ ${BATS_FORMATTER_TEST_TIMEOUT-x} != x ]]; then
timeout "${BATS_FORMATTER_TEST_DURATION:-}" "${BATS_FORMATTER_TEST_TIMEOUT}s"
((++timed_out))
else
fail "${BATS_FORMATTER_TEST_DURATION:-}"
((++failures))
fi
}
bats_tap_stream_comment() { # <comment> <scope>
local scope=$2
# count the lines we printed after the begin text,
if [[ $line_backoff_count -eq 0 && $scope == begin ]]; then
# if this is the first line after begin, go down one line
buffer "\n"
((++line_backoff_count)) # prefix-increment to avoid "error" due to returning 0
fi
((++line_backoff_count))
((line_backoff_count += ${#1} / screen_width)) # account for linebreaks due to length
log "$1"
}
bats_tap_stream_suite() {
#test_file="$1"
line_backoff_count=0
index=
# indicate filename for failures
local file_name="${1#"$BASE_PATH"}"
name="File $file_name"
set_color 4 bold
buffer "%s\n" "$file_name"
clear_color
}
line_backoff_count=0
bats_tap_stream_unknown() { # <full line> <scope>
local scope=$2
# count the lines we printed after the begin text, (or after suite, in case of syntax errors)
if [[ $line_backoff_count -eq 0 && ($scope == begin || $scope == suite) ]]; then
# if this is the first line after begin, go down one line
buffer "\n"
((++line_backoff_count)) # prefix-increment to avoid "error" due to returning 0
fi
((++line_backoff_count))
((line_backoff_count += ${#1} / screen_width)) # account for linebreaks due to length
buffer "%s\n" "$1"
flush
}
bats_parse_internal_extended_tap
summary
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -e
trap '' INT
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/formatter.bash"
bats_tap_stream_plan() {
printf "1..%d\n" "$1"
}
bats_tap_stream_begin() { #<test index> <test name>
:
}
bats_tap_stream_ok() { # [<test index> <test name>
printf "ok %d %s" "$1" "$2"
if [[ "${BATS_FORMATTER_TEST_DURATION-x}" != x ]]; then
printf " # in %d ms" "$BATS_FORMATTER_TEST_DURATION"
fi
printf "\n"
}
bats_tap_stream_not_ok() { # <test index> <test name>
printf "not ok %d %s" "$1" "$2"
if [[ "${BATS_FORMATTER_TEST_DURATION-x}" != x ]]; then
printf " # in %d ms" "$BATS_FORMATTER_TEST_DURATION"
fi
if [[ "${BATS_FORMATTER_TEST_TIMEOUT-x}" != x ]]; then
printf " # timeout after %d s" "${BATS_FORMATTER_TEST_TIMEOUT}"
fi
printf "\n"
}
bats_tap_stream_skipped() { # <test index> <test name> <reason>
if [[ $# -eq 3 ]]; then
printf "ok %d %s # skip %s\n" "$1" "$2" "$3"
else
printf "ok %d %s # skip\n" "$1" "$2"
fi
}
bats_tap_stream_comment() { # <comment text without leading '# '>
printf "# %s\n" "$1"
}
bats_tap_stream_suite() { # <file name>
:
}
bats_tap_stream_unknown() { # <full line>
printf "%s\n" "$1"
}
bats_parse_internal_extended_tap
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -e
yaml_block_open=''
add_yaml_entry() {
if [[ -z "$yaml_block_open" ]]; then
printf " ---\n"
fi
printf " %s: %s\n" "$1" "$2"
yaml_block_open=1
}
close_previous_yaml_block() {
if [[ -n "$yaml_block_open" ]]; then
printf " ...\n"
yaml_block_open=''
fi
}
trap '' INT
number_of_printed_log_lines_for_this_test_so_far=0
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/formatter.bash"
bats_tap_stream_plan() {
printf "TAP version 13\n"
printf "1..%d\n" "$1"
}
bats_tap_stream_begin() { #<test index> <test name>
:
}
bats_tap_stream_ok() { # <test index> <test name>
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
printf "ok %d %s\n" "$1" "$2"
if [[ "${BATS_FORMATTER_TEST_DURATION-x}" != x ]]; then
add_yaml_entry "duration_ms" "${BATS_FORMATTER_TEST_DURATION}"
fi
}
pass_on_optional_data() {
if [[ "${BATS_FORMATTER_TEST_DURATION-x}" != x ]]; then
add_yaml_entry "duration_ms" "${BATS_FORMATTER_TEST_DURATION}"
fi
if [[ "${BATS_FORMATTER_TEST_TIMEOUT-x}" != x ]]; then
add_yaml_entry "timeout_sec" "${BATS_FORMATTER_TEST_TIMEOUT}"
fi
}
bats_tap_stream_not_ok() { # <test index> <test name>
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
printf "not ok %d %s\n" "$1" "$2"
pass_on_optional_data
}
bats_tap_stream_skipped() { # <test index> <test name> <reason>
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
printf "not ok %d %s # SKIP %s\n" "$1" "$2" "$3"
pass_on_optional_data
}
bats_tap_stream_comment() { # <comment text without leading '# '>
if [[ $number_of_printed_log_lines_for_this_test_so_far -eq 0 ]]; then
add_yaml_entry "message" "|" # use a multiline string for this entry
fi
((++number_of_printed_log_lines_for_this_test_so_far))
printf " %s\n" "$1"
}
bats_tap_stream_suite() { # <file name>
:
}
bats_tap_stream_unknown() { # <full line>
:
}
bats_parse_internal_extended_tap
# close the final block if there was one
close_previous_yaml_block
+310
View File
@@ -0,0 +1,310 @@
#!/usr/bin/env bash
set -eET
args=("$@")
filter_tags_list=()
included_tests=()
excluded_tests=()
# shellcheck source=lib/bats-core/common.bash disable=SC2153
source "$BATS_ROOT/lib/bats-core/common.bash"
# shellcheck source=lib/bats-core/preprocessing.bash
source "$BATS_ROOT/lib/bats-core/preprocessing.bash"
abort() {
printf 'ERROR: '
# shellcheck disable=SC2059
printf "$@"
exit 1
} >&2
read_tags() {
local IFS=,
read -ra tags <<<"$1" || true
if ((${#tags[@]} > 0)); then
for ((i = 0; i < ${#tags[@]}; ++i)); do
bats_trim "tags[$i]" "${tags[$i]}"
done
bats_sort sorted_tags "${tags[@]}"
filter_tags_list+=("${sorted_tags[*]}")
else
filter_tags_list+=("")
fi
}
while [[ "$#" -ne 0 ]]; do
case "$1" in
--dummy-flag)
;;
--filter-tags)
shift
read_tags "$1"
;;
--)
shift 1
break
;;
*)
abort "Unknown flag %s in command:\nbats-gather-tests %s" "$1" "${args[*]}"
;;
esac
shift 1
done
# shellcheck source=lib/bats-core/test_functions.bash disable=SC2153
# required to provide e.g. `load` for free code (some users rely on this)
source "$BATS_ROOT/lib/bats-core/test_functions.bash"
# _bats_test_functions_setup will be called per file further down
# override test_functions.bash's version to use it for test registration
bats_test_function() {
local -a tags=()
local description=
while (( $# > 0 )); do
case "$1" in
--description)
description=$2
shift 2
;;
--tags)
IFS=, read -ra tags <<<"$2" || true
shift 2
;;
--)
shift
break
;;
*)
printf "ERROR: unknown option %s for bats_test_function" "$1" >&2
exit 1
;;
esac
done
line="$BATS_TEST_FILENAME"
line+=$'\t'
line+="$*"
# always execute should_skip_because_of_status for its sideeffects
if should_skip_because_of_status || should_skip_because_of_focus || should_skip_because_of_filter_tags || should_skip_because_of_filter; then
return 0
fi
# dereferencing ${test_names[*]} fails on older bash versions when empty -> check before
if [[ ${#test_names[@]} -gt 0 && " ${test_names[*]} " == *" $line "* ]]; then
test_dupes+=("$line")
fi
test_names+=("$line")
local args
printf -v args '%q ' "$@"
#echo "Adding test $line as '$BATS_TEST_FILENAME$args'" >&2
printf "%s\t%s\n" "$BATS_TEST_FILENAME" "$args" >> "$TESTS_LIST_FILE"
}
function should_skip_because_of_focus() {
if bats_all_in tags 'bats:focus'; then
if [[ $focus_mode == 1 ]]; then
# focused tests in focus mode should just be registered
:
else
# the current test enables focus mode ...
focus_mode=1
# ... -> remove previously found, unfocused tests
included_tests=()
: > "$TESTS_LIST_FILE"
fi
elif [[ $focus_mode == 1 ]]; then
# the current test is not focused but focus mode is enabled -> filter out
return 0
# no else -> unfocused tests outside focus mode should just be registered
fi
return 1
}
function should_skip_because_of_filter_tags() {
if (( ${#filter_tags_list[@]} > 0 )); then
for filter_tags in "${filter_tags_list[@]}"; do
# empty search tags only match empty test tags!
if [[ -z "$filter_tags" ]]; then
if [[ ${#tags[@]} -eq 0 ]]; then
return 1
fi
continue
fi
# non empty filter tags must be processed
local -a positive_filter_tags=() negative_filter_tags=()
IFS=, read -ra filter_tags <<<"$filter_tags" || true
for filter_tag in "${filter_tags[@]}"; do
if [[ $filter_tag == !* ]]; then
bats_trim filter_tag "${filter_tag#!}"
negative_filter_tags+=("${filter_tag}")
else
positive_filter_tags+=("${filter_tag}")
fi
done
if bats_append_arrays_as_args positive_filter_tags -- bats_all_in tags &&
! bats_append_arrays_as_args negative_filter_tags -- bats_any_in tags; then
return 1
fi
done
return 0 # skip, because no match was found
fi
return 1 # no filter tags -> nothing to skip
}
if [[ -n "${filter-}" ]]; then
function should_skip_because_of_filter() {
# shellcheck disable=SC2154 # filter should be inherited as env var
! [[ "$description" =~ $filter ]]
}
else
function should_skip_because_of_filter() {
# skip this when there is no $filter
return 1
}
fi
function should_skip_because_of_status() {
# disable this filter if not activated by $filter_status
return 1
}
# shellcheck disable=SC2154 # filter_status is set in the environment
if [[ -n "${filter_status-}" ]]; then
case "$filter_status" in
failed)
bats_filter_test_by_status() { # <line>
! bats_binary_search "$1" "passed_tests"
}
;;
passed)
bats_filter_test_by_status() {
! bats_binary_search "$1" "failed_tests"
}
;;
missed)
bats_filter_test_by_status() {
! bats_binary_search "$1" "failed_tests" && ! bats_binary_search "$1" "passed_tests"
}
;;
*)
printf "Error: Unknown value '%s' for --filter-status. Valid values are 'failed' and 'missed'.\n" "$filter_status" >&2
exit 1
;;
esac
if IFS='' read -d $'\n' -r BATS_PREVIOUS_RUNLOG_FILE < <(ls -1r "$BATS_RUN_LOGS_DIRECTORY"); then
BATS_PREVIOUS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/$BATS_PREVIOUS_RUNLOG_FILE"
if [[ $BATS_PREVIOUS_RUNLOG_FILE == "$BATS_RUNLOG_FILE" ]]; then
count=$(find "$BATS_RUN_LOGS_DIRECTORY" -name "$BATS_RUNLOG_DATE*" | wc -l)
BATS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/${BATS_RUNLOG_DATE}-$count.log"
fi
failed_tests=()
passed_tests=()
# store tests that were already filtered out in the last run for the same filter reason
last_filtered_tests=()
i=0
while read -rd $'\n' line; do
((++i))
case "$line" in
"passed "*)
passed_tests+=("${line#passed }")
;;
"failed "*)
failed_tests+=("${line#failed }")
;;
"status-filtered $filter_status"*) # pick up tests that were filtered in the last round for the same status
last_filtered_tests+=("${line#status-filtered "$filter_status" }")
;;
"status-filtered "*) # ignore other status-filtered lines
;;
"#"*) # allow for comments
;;
*)
printf "Error: %s:%d: Invalid format: %s\n" "$BATS_PREVIOUS_RUNLOG_FILE" "$i" "$line" >&2
exit 1
;;
esac
done < <(sort "$BATS_PREVIOUS_RUNLOG_FILE")
# enable filter only, when there is something to filter
function should_skip_because_of_status() {
#echo "Match:"
#echo "$line"
#printf "%s\n" "${failed_tests[@]}"
#echo "or"
#printf "%s\n" "${last_filtered_tests[@]}"
if bats_filter_test_by_status "$line"; then
if ! bats_binary_search "$line" last_filtered_tests; then
included_tests+=("$line")
#echo "included"
return 1
fi
fi
#echo "excluded"
excluded_tests+=("$line")
return 0
} >&2
else
printf "No recording of previous runs found. Running all tests!\n" >&2
fi
fi
# shellcheck source=lib/bats-core/tracing.bash
source "$BATS_ROOT/lib/bats-core/tracing.bash"
bats_gather_tests_exit_trap() {
local bats_gather_tests_exit_status=$?
trap - ERR EXIT DEBUG
if [[ ${BATS_ERROR_STATUS:-0} != 0 ]]; then
bats_gather_tests_exit_status=$BATS_ERROR_STATUS
printf "1..1\nnot ok 1 bats-gather-tests\n"
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}"
bats_print_failed_command "${stack_trace[@]}"
fi >&2
exit "$bats_gather_tests_exit_status"
}
trap bats_gather_tests_exit_trap EXIT
bats_set_stacktrace_limit
bats_setup_tracing
focus_mode=0
for filename in "$@"; do
if [[ ! -f "$filename" ]]; then
abort 'Test file "%s" does not exist.\n' "${filename}"
fi
test_names=()
test_dupes=()
BATS_TEST_FILENAME="$filename"
_bats_test_functions_setup -1 # invalid TEST_NUMBER, as this is not a test
BATS_TEST_NAME=source
BATS_TEST_FILTER="$BATS_TEST_FILTER" bats_preprocess_source # uses BATS_TEST_FILENAME
# shellcheck disable=SC1090
BATS_TEST_DIRNAME="${filename%/*}" source "$BATS_TEST_SOURCE"
if [[ "${#test_dupes[@]}" -ne 0 ]]; then
abort 'Duplicate test name(s) in file "%s": %s' "${filename}" "${test_dupes[*]#$filename$'\t'}"
fi
done
if [[ -n "$filter_status" ]]; then
# save filtered tests to exclude them again in next round
for test_line in "${excluded_tests[@]}"; do
printf "status-filtered %s %s\n" "$filter_status" "$test_line"
done >>"$BATS_RUNLOG_FILE"
if [[ ${#test_names[@]} -eq 0 && ${#included_tests[@]} -eq 0 ]]; then
printf "There were no tests of status '%s' in the last recorded run.\n" "$filter_status" >&2
fi
fi
if (( focus_mode )); then
printf "focus_mode\n"
fi
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env bash
set -e
bats_encode_test_name() {
local name="$1"
local result='test_'
local hex_code
if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then
name="${name//_/-5f}"
name="${name//-/-2d}"
name="${name// /_}"
result+="$name"
else
local length="${#name}"
local char i
for ((i = 0; i < length; i++)); do
char="${name:$i:1}"
if [[ "$char" == ' ' ]]; then
result+='_'
elif [[ "$char" =~ [[:alnum:]] ]]; then
result+="$char"
else
printf -v 'hex_code' -- '-%02x' \'"$char"
result+="$hex_code"
fi
done
fi
printf -v "$2" '%s' "$result"
}
BATS_TEST_PATTERN="^[[:blank:]]*@test[[:blank:]]+(.*[^[:blank:]])[[:blank:]]+\{(.*)\$"
BATS_TEST_PATTERN_COMMENT="[[:blank:]]*([^[:blank:]()]+)[[:blank:]]*\(?\)?[[:blank:]]+\{[[:blank:]]+#[[:blank:]]*@test[[:blank:]]*\$"
BATS_COMMENT_COMMAND_PATTERN="^[[:blank:]]*#[[:blank:]]*bats[[:blank:]]+(.*)$"
BATS_VALID_TAG_PATTERN="[-_:[:alnum:]]+"
BATS_VALID_TAGS_PATTERN="^ *($BATS_VALID_TAG_PATTERN)?( *, *$BATS_VALID_TAG_PATTERN)* *$"
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash"
extract_tags() { # <tag_type/return_var> <tags-string>
local -r tag_type=$1 tags_string=$2
local -a tags=()
if [[ $tags_string =~ $BATS_VALID_TAGS_PATTERN ]]; then
IFS=, read -ra tags <<<"$tags_string"
local -ri length=${#tags[@]}
for ((i = 0; i < length; ++i)); do
local element="tags[$i]"
bats_trim "$element" "${!element}" 2>/dev/null # printf on bash 3 will complain but work anyways
if [[ -z "${!element}" && -n "${CHECK_BATS_COMMENT_COMMANDS:-}" ]]; then
printf "%s:%d: Error: Invalid %s: '%s'. " "$test_file" "$line_number" "$tag_type" "$tags_string"
printf "Tags must not be empty. Please remove redundant commas!\n"
exit_code=1
fi
done
elif [[ -n "${CHECK_BATS_COMMENT_COMMANDS:-}" ]]; then
printf "%s:%d: Error: Invalid %s: '%s'. " "$test_file" "$line_number" "$tag_type" "$tags_string"
printf "Valid tags must match %s and be separated with comma (and optional spaces)\n" "$BATS_VALID_TAG_PATTERN"
exit_code=1
fi >&2
if ((${#tags[@]} > 0)); then
eval "$tag_type=(\"\${tags[@]}\")"
else
eval "$tag_type=()"
fi
}
test_file="$1"
test_tags=()
# shellcheck disable=SC2034 # used in `bats_sort tags`/`extract_tags``
file_tags=()
line_number=0
exit_code=0
EMPTY_BODY_REGEX='[[:space:]]*\}'
IFS=,
{
while IFS= read -r line; do
((++line_number))
line="${line//$'\r'/}"
if [[ "$line" =~ $BATS_TEST_PATTERN ]] || [[ "$line" =~ $BATS_TEST_PATTERN_COMMENT ]]; then
name="${BASH_REMATCH[1]#[\'\"]}"
name="${name%[\'\"]}"
body="${BASH_REMATCH[2]:-}"
bats_encode_test_name "$name" 'encoded_name'
if [[ "$body" =~ $EMPTY_BODY_REGEX ]]; then
# ":;" is needed for empty {} after test
lead=':; '
else
# avoid injecting non user code into tests
lead=''
fi
bats_append_arrays_as_args \
test_tags file_tags \
-- bats_sort tags
# shellcheck disable=SC2154 # encoded_name is declare via bats_encode_test_name
printf 'bats_test_function --description %q --tags "%s" -- %s;' "$name" "${tags[*]-}" "$encoded_name"
printf '%s() { %s%s\n' "${encoded_name:?}" "$lead" "$body" || :
# shellcheck disable=SC2034 # used in `bats_sort tags`/`extract_tags`
test_tags=() # reset test tags for next test
else
if [[ "$line" =~ $BATS_COMMENT_COMMAND_PATTERN ]]; then
command=${BASH_REMATCH[1]}
case $command in
'test_tags='*)
extract_tags test_tags "${command#test_tags=}"
;;
'file_tags='*)
extract_tags file_tags "${command#file_tags=}"
;;
esac
fi
printf '%s\n' "$line"
fi
done
} <<<"$(<"$test_file")"$'\n'
exit $exit_code