diff --git a/libexec/bats b/libexec/bats index 06f8fab..e074747 100755 --- a/libexec/bats +++ b/libexec/bats @@ -26,35 +26,58 @@ help() { echo } +BATS_READLINK= + resolve_link() { - $(type -p greadlink readlink | head -1) "$1" + if [[ -z "$BATS_READLINK" ]]; then + if command -v 'greadlink' >/dev/null; then + BATS_READLINK='greadlink' + elif command -v 'readlink' >/dev/null; then + BATS_READLINK='readlink' + else + BATS_READLINK='true' + fi + fi + "$BATS_READLINK" "$1" || return 0 } abs_dirname() { - local cwd="$(pwd)" + local cwd="$PWD" local path="$1" while [ -n "$path" ]; do cd "${path%/*}" local name="${path##*/}" - path="$(resolve_link "$name" || true)" + path="$(resolve_link "$name")" done - pwd + printf -v "$2" -- '%s' "$PWD" cd "$cwd" } expand_path() { - { cd "$(dirname "$1")" 2>/dev/null - local dirname="$PWD" + local path="${1%/}" + local dirname="${path%/*}" + + if [[ "$dirname" == "$path" ]]; then + dirname="$PWD" + elif cd "$dirname" 2>/dev/null; then + dirname="$PWD" cd "$OLDPWD" - echo "$dirname/$(basename "$1")" - } || echo "$1" + else + printf '%s' "$path" + return + fi + printf -v "$2" '%s/%s' "$dirname" "${path##*/}" } -BATS_LIBEXEC="$(abs_dirname "$0")" -export BATS_PREFIX="$(abs_dirname "$BATS_LIBEXEC")" -export BATS_CWD="$(abs_dirname .)" +abs_dirname "$0" 'BATS_LIBEXEC' +abs_dirname "$BATS_LIBEXEC" 'BATS_PREFIX' +abs_dirname '.' 'BATS_CWD' + +export BATS_PREFIX +export BATS_CWD +export BATS_TEST_PATTERN='^ *@test +(.+) +\{ *(.*)$' export PATH="$BATS_LIBEXEC:$PATH" options=() @@ -113,14 +136,16 @@ fi filenames=() for filename in "${arguments[@]}"; do + expand_path "$filename" 'filename' + if [ -d "$filename" ]; then shopt -s nullglob - for suite_filename in "$(expand_path "$filename")"/*.bats; do + for suite_filename in "$filename"/*.bats; do filenames["${#filenames[@]}"]="$suite_filename" done shopt -u nullglob else - filenames["${#filenames[@]}"]="$(expand_path "$filename")" + filenames["${#filenames[@]}"]="$filename" fi done @@ -130,13 +155,11 @@ else command="bats-exec-suite" fi -if [ -n "$pretty" ]; then +set -o pipefail execfail +if [ -z "$pretty" ]; then + exec "$command" $count_flag "${filenames[@]}" +else extended_syntax_flag="-x" formatter="bats-format-tap-stream" -else - extended_syntax_flag="" - formatter="cat" + exec "$command" $count_flag $extended_syntax_flag "${filenames[@]}" | "$formatter" fi - -set -o pipefail execfail -exec "$command" $count_flag $extended_syntax_flag "${filenames[@]}" | "$formatter" diff --git a/libexec/bats-exec-suite b/libexec/bats-exec-suite index 29ab255..96021bb 100755 --- a/libexec/bats-exec-suite +++ b/libexec/bats-exec-suite @@ -17,7 +17,11 @@ trap "kill 0; exit 1" int count=0 for filename in "$@"; do - let count+="$(bats-exec-test -c "$filename")" + while IFS= read -r line; do + if [[ "$line" =~ $BATS_TEST_PATTERN ]]; then + let count+=1 + fi + done <"$filename" done if [ -n "$count_only_flag" ]; then diff --git a/libexec/bats-exec-test b/libexec/bats-exec-test index bdce3c8..b27a941 100755 --- a/libexec/bats-exec-test +++ b/libexec/bats-exec-test @@ -26,7 +26,7 @@ else shift fi -BATS_TEST_DIRNAME="$(dirname "$BATS_TEST_FILENAME")" +BATS_TEST_DIRNAME="${BATS_TEST_FILENAME%/*}" BATS_TEST_NAMES=() load() { @@ -39,10 +39,10 @@ load() { filename="$BATS_TEST_DIRNAME/${name}.bash" fi - [ -f "$filename" ] || { + if [[ ! -f "$filename" ]]; then echo "bats: $filename does not exist" >&2 exit 1 - } + fi source "${filename}" } @@ -101,31 +101,33 @@ bats_capture_stack_trace() { local teardown_pattern=" teardown $BATS_TEST_SOURCE" local frame - local index=1 + local i - while frame="$(caller "$index")"; do + for ((i=2; i != ${#FUNCNAME[@]}; ++i)); do + frame="${BASH_LINENO[$((i-1))]} ${FUNCNAME[$i]} ${BASH_SOURCE[$i]}" BATS_CURRENT_STACK_TRACE["${#BATS_CURRENT_STACK_TRACE[@]}"]="$frame" if [[ "$frame" = *"$test_pattern" || \ "$frame" = *"$setup_pattern" || \ "$frame" = *"$teardown_pattern" ]]; then break - else - let index+=1 fi done - BATS_SOURCE="$(bats_frame_filename "${BATS_CURRENT_STACK_TRACE[0]}")" - BATS_LINENO="$(bats_frame_lineno "${BATS_CURRENT_STACK_TRACE[0]}")" + bats_frame_filename "${BATS_CURRENT_STACK_TRACE[0]}" 'BATS_SOURCE' + bats_frame_lineno "${BATS_CURRENT_STACK_TRACE[0]}" 'BATS_LINENO' } bats_print_stack_trace() { local frame local index=1 local count="${#@}" + local filename + local lineno for frame in "$@"; do - local filename="$(bats_trim_filename "$(bats_frame_filename "$frame")")" - local lineno="$(bats_frame_lineno "$frame")" + bats_frame_filename "$frame" 'filename' + bats_trim_filename "$filename" 'filename' + bats_frame_lineno "$frame" 'lineno' if [ $index -eq 1 ]; then echo -n "# (" @@ -133,7 +135,8 @@ bats_print_stack_trace() { echo -n "# " fi - local fn="$(bats_frame_function "$frame")" + local fn + bats_frame_function "$frame" 'fn' if [ "$fn" != "$BATS_TEST_NAME" ]; then echo -n "from function \`$fn' " fi @@ -151,12 +154,16 @@ bats_print_stack_trace() { bats_print_failed_command() { local frame="$1" local status="$2" - local filename="$(bats_frame_filename "$frame")" - local lineno="$(bats_frame_lineno "$frame")" + local filename + local lineno + local failed_line + local failed_command - local failed_line="$(bats_extract_line "$filename" "$lineno")" - local failed_command="$(bats_strip_string "$failed_line")" - echo -n "# \`${failed_command}' " + bats_frame_filename "$frame" 'filename' + bats_frame_lineno "$frame" 'lineno' + bats_extract_line "$filename" "$lineno" 'failed_line' + bats_strip_string "$failed_line" 'failed_command' + printf '%s' "# \`${failed_command}' " if [ $status -eq 1 ]; then echo "failed" @@ -166,49 +173,46 @@ bats_print_failed_command() { } bats_frame_lineno() { - local frame="$1" - local lineno="${frame%% *}" - echo "$lineno" + printf -v "$2" '%s' "${1%% *}" } bats_frame_function() { - local frame="$1" - local rest="${frame#* }" - local fn="${rest%% *}" - echo "$fn" + local __bff_function="${1#* }" + printf -v "$2" '%s' "${__bff_function%% *}" } bats_frame_filename() { - local frame="$1" - local rest="${frame#* }" - local filename="${rest#* }" + local __bff_filename="${1#* }" + __bff_filename="${__bff_filename#* }" - if [ "$filename" = "$BATS_TEST_SOURCE" ]; then - echo "$BATS_TEST_FILENAME" - else - echo "$filename" + if [ "$__bff_filename" = "$BATS_TEST_SOURCE" ]; then + __bff_filename="$BATS_TEST_FILENAME" fi + printf -v "$2" '%s' "$__bff_filename" } bats_extract_line() { - local filename="$1" - local lineno="$2" - sed -n "${lineno}p" "$filename" + local __bats_extract_line_line + local __bats_extract_line_index='0' + + while IFS= read -r __bats_extract_line_line; do + if [[ "$((++__bats_extract_line_index))" -eq "$2" ]]; then + printf -v "$3" '%s' "${__bats_extract_line_line%$'\r'}" + break + fi + done <"$1" } bats_strip_string() { - local string="$1" - printf "%s" "$string" | sed -e "s/^[ "$'\t'"]*//" -e "s/[ "$'\t'"]*$//" + [[ "$1" =~ ^[[:space:]]*(.*)[[:space:]]*$ ]] + printf -v "$2" '%s' "${BASH_REMATCH[1]}" } bats_trim_filename() { - local filename="$1" - local length="${#BATS_CWD}" - - if [ "${filename:0:length+1}" = "${BATS_CWD}/" ]; then - echo "${filename:length+1}" + if [[ "$1" =~ ^${BATS_CWD}/ ]]; then + printf -v "$2" '%s' "${1#$BATS_CWD/}" else - echo "$filename" + printf -v "$2" '%s' "$1" fi } @@ -289,7 +293,7 @@ bats_perform_tests() { bats_perform_test() { BATS_TEST_NAME="$1" - if [ "$(type -t "$BATS_TEST_NAME" || true)" = "function" ]; then + if declare -F "$BATS_TEST_NAME" >/dev/null; then BATS_TEST_NUMBER="$2" if [ -z "$BATS_TEST_NUMBER" ]; then echo "1..1" @@ -322,7 +326,7 @@ BATS_OUT="${BATS_TMPNAME}.out" bats_preprocess_source() { BATS_TEST_SOURCE="${BATS_TMPNAME}.src" - { tr -d '\r' < "$BATS_TEST_FILENAME"; echo; } | bats-preprocess > "$BATS_TEST_SOURCE" + . bats-preprocess <<< "$(< "$BATS_TEST_FILENAME")"$'\n' > "$BATS_TEST_SOURCE" trap "bats_cleanup_preprocessed_source" err exit trap "bats_cleanup_preprocessed_source; exit 1" int } diff --git a/libexec/bats-format-tap-stream b/libexec/bats-format-tap-stream index 614768f..b13fe1f 100755 --- a/libexec/bats-format-tap-stream +++ b/libexec/bats-format-tap-stream @@ -65,9 +65,15 @@ log() { } summary() { - printf "\n%d test%s" "$count" "$(plural "$count")" + printf "\n%d test" "$count" + if [[ "$count" -ne '1' ]]; then + printf 's' + fi - printf ", %d failure%s" "$failures" "$(plural "$failures")" + printf ", %d failure" "$failures" + if [[ "$failures" -ne '1' ]]; then + printf 's' + fi if [ "$skipped" -gt 0 ]; then printf ", %d skipped" "$skipped" @@ -79,7 +85,9 @@ summary() { printf_with_truncation() { local width="$1" shift - local string="$(printf "$@")" + local string + + printf -v 'string' -- "$@" if [ "${#string}" -gt "$width" ]; then printf "%s..." "${string:0:$(( $width - 4 ))}" @@ -105,18 +113,18 @@ advance() { set_color() { local color="$1" - local weight="$2" - printf "\x1B[%d;%dm" $(( 30 + $color )) "$( [ "$weight" = "bold" ] && echo 1 || echo 22 )" + local weight='22' + + if [[ "$2" == 'bold' ]]; then + weight='1' + fi + printf "\x1B[%d;%dm" $(( 30 + $color )) "$weight" } clear_color() { printf "\x1B[0m" } -plural() { - [ "$1" -eq 1 ] || echo "s" -} - _buffer="" buffer() { diff --git a/libexec/bats-preprocess b/libexec/bats-preprocess index 04297ed..8307b76 100755 --- a/libexec/bats-preprocess +++ b/libexec/bats-preprocess @@ -4,6 +4,7 @@ set -e encode_name() { local name="$1" local result="test_" + local hex_code if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then name="${name//_/-5f}" @@ -21,27 +22,28 @@ encode_name() { elif [[ "$char" =~ [[:alnum:]] ]]; then result+="$char" else - result+="$(printf -- "-%02x" \'"$char")" + printf -v 'hex_code' -- "-%02x" \'"$char" + result+="$hex_code" fi done fi - echo "$result" + printf -v "$2" '%s' "$result" } tests=() index=0 -pattern='^ *@test *([^ ].*) *\{ *(.*)$' while IFS= read -r line; do + line="${line//$'\r'}" let index+=1 - if [[ "$line" =~ $pattern ]]; then - quoted_name="${BASH_REMATCH[1]}" + if [[ "$line" =~ $BATS_TEST_PATTERN ]]; then + name="${BASH_REMATCH[1]#[\'\"]}" + name="${name%[\'\"]}" body="${BASH_REMATCH[2]}" - name="$(eval echo "$quoted_name")" - encoded_name="$(encode_name "$name")" + encode_name "$name" 'encoded_name' tests["${#tests[@]}"]="$encoded_name" - echo "${encoded_name}() { bats_test_begin ${quoted_name} ${index}; ${body}" + echo "${encoded_name}() { bats_test_begin \"${name}\" ${index}; ${body}" else printf "%s\n" "$line" fi diff --git a/test/bats.bats b/test/bats.bats index 9fca64a..44b573c 100755 --- a/test/bats.bats +++ b/test/bats.bats @@ -41,25 +41,25 @@ fixtures bats } @test "summary passing tests" { - run filter_control_sequences bats -p $FIXTURE_ROOT/passing.bats + run filter_control_sequences bats -p "$FIXTURE_ROOT/passing.bats" [ $status -eq 0 ] [ "${lines[1]}" = "1 test, 0 failures" ] } @test "summary passing and skipping tests" { - run filter_control_sequences bats -p $FIXTURE_ROOT/passing_and_skipping.bats + run filter_control_sequences bats -p "$FIXTURE_ROOT/passing_and_skipping.bats" [ $status -eq 0 ] [ "${lines[2]}" = "2 tests, 0 failures, 1 skipped" ] } @test "summary passing and failing tests" { - run filter_control_sequences bats -p $FIXTURE_ROOT/failing_and_passing.bats + run filter_control_sequences bats -p "$FIXTURE_ROOT/failing_and_passing.bats" [ $status -eq 0 ] [ "${lines[4]}" = "2 tests, 1 failure" ] } @test "summary passing, failing and skipping tests" { - run filter_control_sequences bats -p $FIXTURE_ROOT/passing_failing_and_skipping.bats + run filter_control_sequences bats -p "$FIXTURE_ROOT/passing_failing_and_skipping.bats" [ $status -eq 0 ] [ "${lines[5]}" = "3 tests, 1 failure, 1 skipped" ] } @@ -268,3 +268,17 @@ fixtures bats [ $status -eq 0 ] [ "${lines[1]}" = "ok 1 loop_func" ] } + +@test "expand variables in test name" { + SUITE='test/suite' run bats "$FIXTURE_ROOT/expand_var_in_test_name.bats" + [ $status -eq 0 ] + [ "${lines[1]}" = "ok 1 test/suite: test with variable in name" ] +} + +@test "handle quoted and unquoted test names" { + run bats "$FIXTURE_ROOT/quoted_and_unquoted_test_names.bats" + [ $status -eq 0 ] + [ "${lines[1]}" = "ok 1 single-quoted name" ] + [ "${lines[2]}" = "ok 2 double-quoted name" ] + [ "${lines[3]}" = "ok 3 unquoted name" ] +} diff --git a/test/fixtures/bats/expand_var_in_test_name.bats b/test/fixtures/bats/expand_var_in_test_name.bats new file mode 100644 index 0000000..8f6dec2 --- /dev/null +++ b/test/fixtures/bats/expand_var_in_test_name.bats @@ -0,0 +1,3 @@ +@test "$SUITE: test with variable in name" { + true +} diff --git a/test/fixtures/bats/quoted_and_unquoted_test_names.bats b/test/fixtures/bats/quoted_and_unquoted_test_names.bats new file mode 100644 index 0000000..aa460da --- /dev/null +++ b/test/fixtures/bats/quoted_and_unquoted_test_names.bats @@ -0,0 +1,11 @@ +@test 'single-quoted name' { + true +} + +@test "double-quoted name" { + true +} + +@test unquoted name { + true +} diff --git a/test/test_helper.bash b/test/test_helper.bash index 84eee8c..302f743 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -1,6 +1,6 @@ fixtures() { FIXTURE_ROOT="$BATS_TEST_DIRNAME/fixtures/$1" - RELATIVE_FIXTURE_ROOT="$(bats_trim_filename "$FIXTURE_ROOT")" + bats_trim_filename "$FIXTURE_ROOT" 'RELATIVE_FIXTURE_ROOT' } setup() {