Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
martin-schulze-vireso committed Jun 15, 2022
1 parent ed58442 commit 0f4a408
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 52 deletions.
37 changes: 37 additions & 0 deletions lib/bats-core/common.bash
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,40 @@ bats_require_minimum_version() { # <required version>
BATS_GUARANTEED_MINIMUM_VERSION="$required_minimum_version"
fi
}

bats_binary_search() { # <search-value> <array-name>
local -r search_value=$1 array_name=$2 start=${3:-0} end=${4:-0}

if [[ $# -ne 2 ]]; then
printf "ERROR: bats_binary_search requires exactly 2 arguments: <search value> <array name>\n" >&2
return 2
fi

# we'd like to test if array is set but we cannot distinguish unset from empty arrays, so we need to skip that

eval "bats_impl_binary_search '$search_value' '$array_name' 0 \${#$array_name[@]}"
}

bats_impl_binary_search () { # <search-value> <array-name> <start (inclusive)> <end (exclusive)>
local -r search_value=$1 array_name=$2 start=${3:-0} end=${4:-0}

# not found if search space is empty
if (( start >= end )); then
return 1
fi

# probe the value in the middle
local mid=$(( (start + end) / 2 ))
eval "local mid_value=\${$array_name[$mid]}"

echo "$*" $mid_value $search_value
if [[ "$mid_value" == "$search_value" ]]; then
return 0
elif [[ "$mid_value" < "$search_value" ]]; then
# This branch excludes equality -> +1 to skip the mid element.
# This +1 also avoids endless recursion on odd sized search ranges.
bats_impl_binary_search "$search_value" "$array_name" $((mid + 1)) $end
else
bats_impl_binary_search "$search_value" "$array_name" $start mid
fi
}
6 changes: 4 additions & 2 deletions libexec/bats-core/bats
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ 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_USER_HOME="${BATS_USER_HOME:-$HOME/.bats}"

if [[ ! -d "${BATS_TMPDIR}" ]];then
printf "Error: BATS_TMPDIR (%s) does not exist or is not a directory" "${BATS_TMPDIR}" >&2
Expand Down Expand Up @@ -246,8 +247,9 @@ while [[ "$#" -ne 0 ]]; do
shift
BATS_CODE_QUOTE_STYLE="$1"
;;
--rerun-failed)
flags+=('--rerun-failed')
--filter-status)
shift
flags+=('--filter-status' "$1")
;;
-*)
abort "Bad command line option '$1'"
Expand Down
3 changes: 0 additions & 3 deletions libexec/bats-core/bats-exec-file
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ while [[ "$#" -ne 0 ]]; do
# use singular to allow for users to override in file
BATS_NO_PARALLELIZE_WITHIN_FILE=1
;;
--rerun-failed)
flags+=(--rerun-failed)
;;
--dummy-flag)
;;
--trace)
Expand Down
103 changes: 81 additions & 22 deletions libexec/bats-core/bats-exec-suite
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ filter=''
num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS:-1}
bats_no_parallelize_across_files=${BATS_NO_PARALLELIZE_ACROSS_FILES-}
bats_no_parallelize_within_files=
bats_rerun_failed=
filter_status=''
flags=('--dummy-flag') # add a dummy flag to prevent unset varialeb errors on empty array expansion in old bash versions
setup_suite_file=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
Expand Down Expand Up @@ -43,9 +43,9 @@ while [[ "$#" -ne 0 ]]; do
bats_no_parallelize_within_files=1
flags+=("--no-parallelize-within-files")
;;
--rerun-failed)
bats_rerun_failed=1
flags+=("--rerun-failed")
--filter-status)
shift
filter_status="$1"
;;
--dummy-flag)
;;
Expand Down Expand Up @@ -124,24 +124,83 @@ bats_gather_tests() {
test_count="${#all_tests[@]}"
}

if [[ -n "$bats_rerun_failed" ]]; then
mkdir -p "$PWD/.bats"
export BATS_RERUN_FAILED_FILE="$PWD/.bats/rerun-failed-tests.list"
export BATS_RERUN_FAILED_TMPFILE="${BATS_RERUN_FAILED_FILE}.tmp"
if [[ -s "$BATS_RERUN_FAILED_FILE" ]]; then
test_count="$(wc -l < "$BATS_RERUN_FAILED_FILE")"
cp "$BATS_RERUN_FAILED_FILE" "$TESTS_LIST_FILE"
elif [[ -f "$BATS_RERUN_FAILED_FILE" ]]; then
printf "There where no failed tests in the last recorded run.\n" >&2
printf "Delete the file '%s' to run all tests again.\n" "${BATS_RERUN_FAILED_FILE#"$PWD/"}" >&2
test_count=0
TEST_ROOT=${1%/*}
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
printf "No recording of previos runs found. Running all tests!\n" >&2
bats_gather_tests "$@"
fi
: >"$BATS_RERUN_FAILED_TMPFILE" # truncate the file if it exists, else create
BATS_RUN_LOGS_DIRECTORY=
fi
# discard via sink instead of having a conditional later
export BATS_RUNLOG_FILE='/dev/null'
else
bats_gather_tests "$@"
export BATS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/$(date -Iseconds).log"
fi

bats_gather_tests "$@"

if [[ -n "$filter_status" ]]; then
source "$BATS_ROOT/lib/bats-core/common.bash"
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 'passing'.\n" >&2
exit 1
;;
esac

if [[ -s "$BATS_RUNLOG_FILE" ]]; then
failed_tests=()
passed_tests=()
i=0
while read -rd $'\n' line; do
((++i))
case "$line" in
"passed "*)
passed_tests+=("${line#passed }")
;;
"failed "*)
failed_tests+=("${line#failed }")
;;
*)
printf "Error: %s:%d: Invalid format: %s\n" "$BATS_RUNLOG_FILE" $i "$line"
exit 1
;;
esac
done < "$BATS_RUNLOG_FILE"

filtered_tests=()
for line in "${all_tests[@]}"; do
if bats_filter_test_by_status "$line"; then
printf "%s\n" "$line"
filtered_tests+=("$line")
fi
done > "$TESTS_LIST_FILE"

test_count="${#filtered_tests[@]}"
if [[ ${#failed_tests} -eq 0 && ${#filtered_tests[@]} -eq 0 ]]; then
printf "There where no failed tests in the last recorded run.\n" >&2
printf "Delete the file '%s' to run all tests again.\n" "${BATS_FAILED_TESTS_FILE#"$PWD/"}" >&2
fi
else
printf "No recording of previous runs found. Running all tests!\n" >&2
fi
fi

if [[ -n "$count_only_flag" ]]; then
Expand Down Expand Up @@ -205,10 +264,10 @@ bats_suite_exit_trap() {
printf "\n# Received SIGINT, aborting ...\n\n"
fi

if [[ -n "$bats_rerun_failed" && "${BATS_INTERRUPTED-NOTSET}" == NOTSET ]]; then
if [[ -d "$BATS_RUN_LOGS_DIRECTORY" && "${BATS_INTERRUPTED-NOTSET}" == NOTSET ]]; then
# only overwrite file once we finished with all tests
# else we would discard tests when aborting a test run with CTRL+C
mv "$BATS_RERUN_FAILED_TMPFILE" "$BATS_RERUN_FAILED_FILE"
mv "$BATS_RUNLOG_TMPFILE" "$BATS_RUNLOG_FILE"
fi
exit "$bats_exec_suite_status"
}
Expand Down
11 changes: 4 additions & 7 deletions libexec/bats-core/bats-exec-test
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ 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:-}"
BATS_RERUN_FAILED=

while [[ "$#" -ne 0 ]]; do
case "$1" in
Expand Down Expand Up @@ -39,9 +38,6 @@ while [[ "$#" -ne 0 ]]; do
shift
BATS_GATHER_TEST_OUTPUTS_IN="$1"
;;
--rerun-failed)
BATS_RERUN_FAILED=1
;;
*)
break
;;
Expand Down Expand Up @@ -128,9 +124,6 @@ bats_exit_trap() {
local print_bats_out="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS}"

if [[ -z "$BATS_TEST_COMPLETED" || -z "$BATS_TEARDOWN_COMPLETED" || "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
if [[ -n "$BATS_RERUN_FAILED" ]]; then
printf "%s\t%s\n" "$BATS_TEST_FILENAME" "$BATS_TEST_NAME" >> "${BATS_RERUN_FAILED_TMPFILE}"
fi
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).
Expand All @@ -155,12 +148,16 @@ bats_exit_trap() {

print_bats_out=1
status=1
local state=failed
else
printf 'ok %d %s%s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" \
"$skipped" >&3
status=0
local state=passed
fi

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
Expand Down
42 changes: 29 additions & 13 deletions test/bats.bats
Original file line number Diff line number Diff line change
Expand Up @@ -1234,29 +1234,41 @@ END_OF_ERR_MSG
TMPDIR=/tmp/ bats "$FIXTURE_ROOT/BATS_variables_dont_contain_double_slashes.bats"
}

@test "Without previous recording --rerun-failed runs all tests and then reruns only failed tests" {
@test "Without .bats/run-logs --filter-status failed returns an error" {
bats_require_minimum_version 1.5.0
run -1 bats --filter-status failed "$FIXTURE_ROOT/passing_and_failing.bats"
[[ "${lines[0]}" == "Error: --filter-status needs"*".bats/run-logs/' to save failed tests. Please create this folder, add it to .gitignore and try again." ]] || false
}

@test "Without previous recording --filter-status failed runs all tests and then runs only failed tests" {
cd "$BATS_TEST_TMPDIR" # don't pollute the source folder
run -1 bats --rerun-failed "$FIXTURE_ROOT/passing_and_failing.bats"
cp "$FIXTURE_ROOT/passing_and_failing.bats" .
mkdir -p .bats/run-logs
bats_require_minimum_version 1.5.0
run -1 bats --filter-status failed "passing_and_failing.bats"
# without previous recording, all tests should be run
[ "${lines[0]}" == 'No recording of previos runs found. Running all tests!' ]
[ "${lines[0]}" == 'No recording of previous runs found. Running all tests!' ]
[ "${lines[1]}" == '1..2' ]
[ "${lines[2]}" == 'ok 1 a passing test' ]
[ "${lines[3]}" == 'not ok 2 a failing test' ]

run -1 bats --tap --rerun-failed "$FIXTURE_ROOT/passing_and_failing.bats"
run -1 bats --tap --filter-status failed "passing_and_failing.bats"
# now we should only run the failing test
[ "${lines[0]}" == 1..1 ]
[ "${lines[1]}" == "not ok 1 a failing test" ]
}

@test "--rerun-failed gives warning on empty failed test list" {
@test "--filter-status failed gives warning on empty failed test list" {
cd "$BATS_TEST_TMPDIR" # don't pollute the source folder
cp "$FIXTURE_ROOT/passing.bats" .
mkdir -p .bats/run-logs
bats_require_minimum_version 1.5.0
# have no failing tests
run -0 bats --rerun-failed "$FIXTURE_ROOT/passing.bats"
# try to rerun the empty list of failing tests
run -0 bats --rerun-failed "$FIXTURE_ROOT/passing.bats"
run -0 bats --filter-status failed "passing.bats"
# try to run the empty list of failing tests
run -0 bats --filter-status failed "passing.bats"
[ "${lines[0]}" == "There where no failed tests in the last recorded run." ]
[ "${lines[1]}" == "Delete the file '.bats/rerun-failed-tests.list' to run all tests again." ]
[ "${lines[1]}" == "Delete the file '.bats/run-logs/failed-tests.list' to run all tests again." ]
[ "${lines[2]}" == "1..0" ]
[ "${#lines[@]}" -eq 3 ]
}
Expand All @@ -1266,26 +1278,30 @@ enforce_own_process_group() {
"$@"
}

@test "--rerun-failed does not update list when run is aborted" {
@test "--filter-status failed does not update list when run is aborted" {
if [[ "$BATS_NUMBER_OF_PARALLEL_JOBS" -gt 1 ]]; then
skip "Aborts don't work in parallel mode"
fi

cd "$BATS_TEST_TMPDIR" # don't pollute the source folder
local RERUN_FILE=".bats/rerun-failed-tests.list"
cp "$FIXTURE_ROOT/sigint_in_failing_test.bats" .
mkdir -p .bats/run-logs
local RERUN_FILE=".bats/run-logs/failed-tests.list"

bats_require_minimum_version 1.5.0
# don't hang yet, so we get a useful rerun file
run -1 env bats --rerun-failed "$FIXTURE_ROOT/sigint_in_failing_test.bats"
run -1 env DONT_ABORT=1 bats "sigint_in_failing_test.bats"

orig_date=$(date -r "$RERUN_FILE")

ls -alR .
# check that we have something to rerun
[ -s "$RERUN_FILE" ]

sleep 1 # ensure we would get different timestamps for each run

# now rerun but abort midrun
run -1 enforce_own_process_group bats --rerun-failed "$FIXTURE_ROOT/sigint_in_failing_test.bats"
run -1 enforce_own_process_group bats --rerun-failed "sigint_in_failing_test.bats"

new_date=$(date -r "$RERUN_FILE")
echo "$orig_date"
Expand Down
27 changes: 27 additions & 0 deletions test/common.bats
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,31 @@
# a lower version shoudl not change
bats_require_minimum_version 0.1.2
[ "${BATS_GUARANTEED_MINIMUM_VERSION}" = 0.2.3 ]
}

@test bats_binary_search {
bats_require_minimum_version 1.5.0
empty_array=()

run -2 bats_binary_search "search-value"
[ "$output" = "ERROR: bats_binary_search requires exactly 2 arguments: <search value> <array name>" ]

# unset array = empty array: a bit unfortunate but we can't tell the difference (on older Bash?)
unset no_array
run -1 bats_binary_search "search-value" "no_array"
run -1 bats_binary_search "search-value" "empty_array"

odd_length_array=(1 2 3)
run -1 bats_binary_search a odd_length_array
run -0 bats_binary_search 1 odd_length_array
run -0 bats_binary_search 2 odd_length_array
run -0 bats_binary_search 3 odd_length_array

even_length_array=(1 2 3 4)
run -1 bats_binary_search a even_length_array
run -0 bats_binary_search 1 even_length_array
run -0 bats_binary_search 2 even_length_array
run -0 bats_binary_search 3 even_length_array
run -0 bats_binary_search 4 even_length_array

}
6 changes: 1 addition & 5 deletions test/fixtures/bats/sigint_in_failing_test.bats
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
setup() {
load '../../concurrent-coordination'
}

@test "failing" {
if [[ -z "${DONT_ABORT:-}" ]]; then
# emulate CTRL-C by sending SIGINT to the whole process group
kill -SIGINT -- -$BATS_ROOT_PID
kill -SIGINT -- -"$BATS_ROOT_PID"
fi
false
}
Expand Down

0 comments on commit 0f4a408

Please sign in to comment.