1
0
mirror of https://github.com/sstephenson/bats.git synced 2024-11-17 11:42:33 +01:00

Pretty test output for terminals

This commit is contained in:
Sam Stephenson 2013-10-21 12:03:45 -05:00
parent b0606bc8cd
commit a3229efbfa
4 changed files with 292 additions and 39 deletions

View File

@ -33,39 +33,48 @@ test passes. In this way, each line is an assertion of truth.
## Running tests ## Running tests
To run your tests, invoke the `bats` interpreter with a path to a test To run your tests, invoke the `bats` interpreter with a path to a test
file. The file's test cases are run sequentially and in isolation, and file. The file's test cases are run sequentially and in isolation. If
the results are written to standard output in human-readable [TAP all the test cases pass, `bats` exits with a `0` status code. If there
format](http://testanything.org/wiki/index.php/TAP_specification#THE_TAP_FORMAT). are any failures, `bats` exits with a `1` status code.
If all the test cases pass, `bats` exits with a `0` status code. If
there are any failures, `bats` exits with a `1` status code. When you run Bats from a terminal, you'll see output as each test is
performed, with a check-mark next to the test's name if it passes or
an "X" if it fails.
$ bats addition.bats $ bats addition.bats
✓ addition using bc
✓ addition using dc
2 tests, 0 failures
If Bats is not connected to a terminal—in other words, if you
run it from a continuous integration system or redirect its output to
a file—the results are displayed in human-readable, machine-parsable
[TAP format](http://testanything.org/wiki/index.php/TAP_specification#THE_TAP_FORMAT).
You can force TAP output from a terminal by invoking Bats with the
`--tap` option.
$ bats --tap addition.bats
1..2 1..2
ok 1 addition using bc ok 1 addition using bc
ok 2 addition using dc ok 2 addition using dc
$ echo $?
0
You can also define special `setup` and `teardown` functions which run
before and after each test case, respectively. Use these to load
fixtures, set up your environment, and clean up when you're done.
### Test suites ### Test suites
You can also invoke the `bats` interpreter with a path to a directory You can invoke the `bats` interpreter with multiple test file
containing multiple `.bats` files. Bats will run each test file arguments, or with a path to a directory containing multiple `.bats`
individually and aggregate the results. If any test case fails, `bats` files. Bats will run each test file individually and aggregate the
exits with a `1` status code. results. If any test case fails, `bats` exits with a `1` status code.
## Helpers and introspection ## Helpers and introspection
### The _run_ helper ### The _run_ helper
If you're using Bats, you're probably most interested in testing a Many Bats tests need to run a command and then make assertions about
command's exit status and output. Bats includes a `run` helper that its exit status and output. Bats includes a `run` helper that invokes
invokes its arguments as a command, saves the exit status and output its arguments as a command, saves the exit status and output into
into special global variables, and then returns with a `0` status code special global variables, and then returns with a `0` status code so
so you can continue to make assertions in your test case. you can continue to make assertions in your test case.
For example, let's say you're testing that the `foo` command, when For example, let's say you're testing that the `foo` command, when
passed a nonexistent filename, exits with a `1` status code and prints passed a nonexistent filename, exits with a `1` status code and prints
@ -138,7 +147,6 @@ Or you can skip conditionally:
```bash ```bash
@test "A test which should run" { @test "A test which should run" {
if [ foo != bar ]; then if [ foo != bar ]; then
skip "foo isn't bar" skip "foo isn't bar"
fi fi
@ -148,6 +156,12 @@ Or you can skip conditionally:
} }
``` ```
### Setup and teardown
You can define special `setup` and `teardown` functions which run
before and after each test case, respectively. Use these to load
fixtures, set up your environment, and clean up when you're done.
### Special variables ### Special variables
There are several global variables you can use to introspect on Bats There are several global variables you can use to introspect on Bats

View File

@ -5,6 +5,27 @@ version() {
echo "Bats 0.2.0" echo "Bats 0.2.0"
} }
usage() {
version
echo "Usage: bats [-c] [-p | -t] <test> [<test> ...]"
}
help() {
usage
echo
echo " <test> is the path to a Bats test file, or the path to a directory"
echo " containing Bats test files."
echo
echo " -c, --count Count the number of test cases without running any tests"
echo " -h, --help Display this help message"
echo " -p, --pretty Show results in pretty format (default for terminals)"
echo " -t, --tap Show results in TAP format"
echo " -v, --version Display the version number"
echo
echo " For more information, see https://github.com/sstephenson/bats"
echo
}
resolve_link() { resolve_link() {
$(type -p greadlink readlink | head -1) "$1" $(type -p greadlink readlink | head -1) "$1"
} }
@ -35,26 +56,61 @@ BATS_LIBEXEC="$(abs_dirname "$0")"
export BATS_PREFIX="$(abs_dirname "$BATS_LIBEXEC")" export BATS_PREFIX="$(abs_dirname "$BATS_LIBEXEC")"
export PATH="$BATS_LIBEXEC:$PATH" export PATH="$BATS_LIBEXEC:$PATH"
if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then options=()
arguments=()
for arg in "$@"; do
if [ "${arg:0:1}" = "-" ]; then
if [ "${arg:1:1}" = "-" ]; then
options[${#options[*]}]="${arg:2}"
else
index=1
while option="${arg:$index:1}"; do
[ -n "$option" ] || break
options[${#options[*]}]="$option"
index=$(($index+1))
done
fi
else
arguments[${#arguments[*]}]="$arg"
fi
done
unset count_flag pretty
[ -t 0 ] && [ -t 1 ] && pretty="1"
for option in "${options[@]}"; do
case "$option" in
"h" | "help" )
help
exit 0
;;
"v" | "version" )
version version
exit 0 exit 0
fi ;;
"c" | "count" )
count_flag="-c"
;;
"t" | "tap" )
pretty=""
;;
"p" | "pretty" )
pretty="1"
;;
* )
usage >&2
exit 1
;;
esac
done
count_only="" if [ "${#arguments[@]}" -eq 0 ]; then
if [ "$1" = "-c" ]; then usage >&2
count_only="-c"
shift
fi
if [ -z "$1" ]; then
{ version
echo "usage: $0 [-c] <filename> [<filename> ...]"
} >&2
exit 1 exit 1
fi fi
filenames=() filenames=()
for filename in "$@"; do for filename in "${arguments[@]}"; do
if [ -d "$filename" ]; then if [ -d "$filename" ]; then
shopt -s nullglob shopt -s nullglob
for suite_filename in "$(expand_path "$filename")"/*.bats; do for suite_filename in "$(expand_path "$filename")"/*.bats; do
@ -67,7 +123,18 @@ for filename in "$@"; do
done done
if [ "${#filenames[@]}" -eq 1 ]; then if [ "${#filenames[@]}" -eq 1 ]; then
exec bats-exec-test $count_only "${filenames[0]}" command="bats-exec-test"
else else
exec bats-exec-suite $count_only "${filenames[@]}" command="bats-exec-suite"
fi fi
if [ -n "$pretty" ]; then
extended_syntax_flag="-x"
formatter="bats-format-tap-stream"
else
extended_syntax_flag=""
formatter="cat"
fi
set -o pipefail execfail
exec "$command" $count_flag $extended_syntax_flag "${filenames[@]}" | "$formatter"

154
libexec/bats-format-tap-stream Executable file
View File

@ -0,0 +1,154 @@
#!/usr/bin/env bash
set -e
# Just stream the TAP output (sans extended syntax) if tput is missing
command -v tput >/dev/null || exec grep -v "^begin "
IFS= read -r header # 1..n
count="${header:3}"
index=0
failures=0
name=""
count_column_width=$(( ${#count} * 2 + 2 ))
update_screen_width() {
screen_width="$(tput cols)"
count_column_left=$(( $screen_width - $count_column_width ))
}
trap update_screen_width WINCH
update_screen_width
begin() {
go_to_column 0
printf_with_truncation $(( $count_column_left - 1 )) " %s" "$name"
clear_to_end_of_line
go_to_column $count_column_left
printf "%${#count}s/${count}" "$index"
go_to_column 1
}
pass() {
go_to_column 0
printf " ✓ %s" "$name"
advance
}
skip() {
local reason="$1"
[ -z "$reason" ] || reason=": $reason"
go_to_column 0
printf " - %s (skipped%s)" "$name" "$reason"
advance
}
fail() {
go_to_column 0
set_color 1 bold
printf " ✗ %s" "$name"
advance
}
log() {
set_color 1
printf " %s\n" "$1"
clear_color
}
summary() {
printf "\n%d test%s, %d failure%s\n" \
"$count" "$(plural "$count")" \
"$failures" "$(plural "$failures")"
}
printf_with_truncation() {
local width="$1"
shift
local string="$(printf "$@")"
if [ "${#string}" -gt "$width" ]; then
printf "%s..." "${string:0:$(( $width - 4 ))}"
else
printf "%s" "$string"
fi
}
go_to_column() {
local column="$1"
tput hpa "$column"
}
clear_to_end_of_line() {
tput el
}
advance() {
clear_to_end_of_line
echo
clear_color
}
set_color() {
local color="$1"
local weight="$2"
tput setaf "$color"
[ -z "$weight" ] || tput "$weight"
}
clear_color() {
tput sgr0
}
plural() {
[ "$1" -eq 1 ] || echo "s"
}
_buffer=""
buffer() {
_buffer="${_buffer}$("$@")"
}
flush() {
printf "%s" "$_buffer"
_buffer=""
}
finish() {
flush
printf "\n"
}
trap finish EXIT
while IFS= read -r line; do
case "$line" in
"begin "* )
index=$(( $index + 1 ))
name="${line#* $index }"
buffer begin
flush
;;
"ok "* )
skip_expr="ok $index # skip (\(([^)]*)\))?"
if [[ "$line" =~ $skip_expr ]]; then
buffer skip "${BASH_REMATCH[2]}"
else
buffer pass
fi
;;
"not ok "* )
failures=$(( $failures + 1 ))
buffer fail
;;
"# "* )
buffer log "${line:5}"
;;
"# "* )
buffer log "${line:2}"
;;
esac
done
buffer summary

View File

@ -6,7 +6,7 @@ fixtures bats
@test "no arguments prints usage instructions" { @test "no arguments prints usage instructions" {
run bats run bats
[ $status -eq 1 ] [ $status -eq 1 ]
[ $(expr "${lines[1]}" : "usage:") -ne 0 ] [ $(expr "${lines[1]}" : "Usage:") -ne 0 ]
} }
@test "-v and --version print version number" { @test "-v and --version print version number" {
@ -15,6 +15,12 @@ fixtures bats
[ $(expr "$output" : "Bats [0-9][0-9.]*") -ne 0 ] [ $(expr "$output" : "Bats [0-9][0-9.]*") -ne 0 ]
} }
@test "-h and --help print help" {
run bats -h
[ $status -eq 0 ]
[ "${#lines[@]}" -gt 3 ]
}
@test "invalid filename prints an error" { @test "invalid filename prints an error" {
run bats nonexistent run bats nonexistent
[ $status -eq 1 ] [ $status -eq 1 ]
@ -126,3 +132,15 @@ fixtures bats
[ "${lines[4]}" = "begin 2 a passing test" ] [ "${lines[4]}" = "begin 2 a passing test" ]
[ "${lines[5]}" = "ok 2 a passing test" ] [ "${lines[5]}" = "ok 2 a passing test" ]
} }
@test "pretty and tap formats" {
run bats --tap "$FIXTURE_ROOT/passing.bats"
tap_output="$output"
[ $status -eq 0 ]
run bats --pretty "$FIXTURE_ROOT/passing.bats"
pretty_output="$output"
[ $status -eq 0 ]
[ "$tap_output" != "$pretty_output" ]
}