| #!/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 |
| file_index=0 |
| for filename in "${BATS_UNIQUE_TEST_FILENAMES[@]}"; do |
| (( ++file_index )) |
| if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then |
| bats_exec_suite_status=130 # bash's code for SIGINT exits |
| break |
| fi |
| bats-exec-file "${flags[@]}" "$file_index" "$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 |