1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
|
#!/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" # append name in case of test retries
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_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
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash"
bats_exit_trap() {
local status
local exit_metadata=''
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 || 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,SC2329
bats_timeout_trap() {
BATS_TIMED_OUT=1
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=
exit 1
}
# find pids of process and all its descendants
bats_find_processes_of() { # <parent-pid>
local -ri parent_pid=$1
child_pids=("$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
# PID PPID child_pids
# 1 0 (2)
# 2 1 (2)
# 3 1 (2)
# 4 3 (2)
# 5 2 (2 5)
# 6 5 (2 5 6)
# assumes pids are in ascending order and there are no orphans in the chain
if (( BASH_VERSINFO[0] < 4 )); then
# BASHPID is not available before bash 4
BASHPID=$(sh -c 'echo $PPID')
fi
while read -ra row; do
local -i ppid=${row[ppid_col]} pid=${row[$pid_col]}
if (( ppid == parent_pid)) || bats_linear_reverse_search "$ppid" child_pids; then
# exclude `ps` command substitution below
if (( pid != BASHPID)); then
child_pids+=("$pid")
fi
fi
done
# MSYS does not support -o and rows are not sorted by pid
} < <(ps -Ao pid,ppid,args 2>/dev/null || { ps -ef | sort -k 2 -h; })
}
# send signal to process and all its descendants
bats_kill_processes_of() { # <parent-pid> [<signal>]
local -ir parent_pid="${1?}"
local -r signal=${2?}
bats_find_processes_of "$parent_pid"
kill "-$signal" "${child_pids[@]}"
}
# sets a timeout for this process
bats_start_timeout_countdown() { # <timeout>
local -ri timeout=$1
local -ri target_pid=$$
# shellcheck disable=SC2064
trap "bats_timeout_trap $target_pid" TERM
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
(
# with sleep in foreground this shell wouldn't receive signals,
# so we use wait below and kill sleep explicitly when signalled to do so
# On Windows the TERM signal does not seem to shut down sleep ->
# close all fds to avoid blocking IO when sleep does not turn off
(eval exec {0..255}">&-"; sleep "$timeout") &
# shellcheck disable=SC2064
trap "kill $!; exit 0" TERM
wait
trap '' TERM
bats_kill_processes_of $target_pid TERM
) &
}
bats_abort_timeout_countdown() {
# kill the countdown process, don't care if its still there
kill "$BATS_killer_pid" &>/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
if [[ -n "${BATS_TEST_TIMEOUT:-}" ]]; then
bats_start_timeout_countdown "$BATS_TEST_TIMEOUT"
declare -r 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" 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" EXIT
bats_teardown_trap "" # 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_OUT="$BATS_RUN_TMPDIR/test/$BATS_SUITE_TEST_NUMBER.out"
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[@]}"
|