#!/usr/bin/env bash set -e count_only_flag='' filter='' num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS:-1} bats_no_parallelize_across_files=${BATS_NO_PARALLELIZE_ACROSS_FILES-} bats_no_parallelize_within_files= flags=('--dummy-flag') # add a dummy flag to prevent unset varialeb errors on empty array expansion in old bash versions abort() { printf 'Error: %s\n' "$1" >&2 exit 1 } while [[ "$#" -ne 0 ]]; do case "$1" in -c) count_only_flag=1 ;; -f) shift filter="$1" flags+=('-f' "$filter") ;; -j) shift num_jobs="$1" flags+=('-j' "$num_jobs") ;; -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") ;; --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 if [[ "$num_jobs" != 1 ]]; then if ! type -p parallel >/dev/null && [[ -z "$bats_no_parallelize_across_files" ]]; then abort "Cannot execute \"${num_jobs}\" jobs without GNU parallel" exit 1 fi # shellcheck source=lib/bats-core/semaphore.bash source "${BATS_ROOT}/lib/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" all_tests=() for filename in "$@"; do if [[ ! -f "$filename" ]]; then abort "Test file \"${filename}\" does not exist" fi test_names=() test_dupes=() while read -r line; do if [[ ! "$line" =~ ^bats_test_function\ ]]; then continue fi line="${line%$'\r'}" line="${line#* }" test_line=$(printf "%s\t%s" "$filename" "$line") all_tests+=("$test_line") printf "%s\n" "$test_line" >>"$TESTS_LIST_FILE" # avoid unbound variable errors on empty array expansion with old bash versions if [[ ${#test_names[@]} -gt 0 && " ${test_names[*]} " == *" $line "* ]]; then test_dupes+=("$line") continue fi test_names+=("$line") done < <(BATS_TEST_FILTER="$filter" bats-preprocess "$filename") if [[ "${#test_dupes[@]}" -ne 0 ]]; then abort "Duplicate test name(s) in file \"${filename}\": ${test_dupes[*]}" fi done test_count="${#all_tests[@]}" if [[ -n "$count_only_flag" ]]; then printf '%d\n' "${test_count}" exit 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" exit 1 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" exit 1 fi # only abort on the lowest levels trap 'BATS_INTERRUPTED=true' INT trap bats_exit_trap EXIT bats_exit_trap() { if [[ ${BATS_INTERRUPTED-NOTSET} != NOTSET ]]; then printf "\n# Received SIGINT, aborting ...\n\n" fi exit "$status" } 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 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 --keep-order --jobs "$num_jobs" bats-exec-file "$(printf "%q " "${flags[@]}")" "{}" "$TESTS_LIST_FILE" ::: "${BATS_UNIQUE_TEST_FILENAMES[@]}" 2>&1 || status=1 else for filename in "${BATS_UNIQUE_TEST_FILENAMES[@]}"; do if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then status=130 # bash's code for SIGINT exits break fi bats-exec-file "${flags[@]}" "$filename" "${TESTS_LIST_FILE}" || status=1 done fi exit "$status"