#!/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 while [[ "$#" -ne 0 ]]; do case "$1" in --dummy-flag) ;; --filter-tags) shift 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[@]}" IFS=, filter_tags_list+=("${sorted_tags[*]}") else filter_tags_list+=("") fi ;; --) 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() { # ! 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