1
0
mirror of https://github.com/sstephenson/bats.git synced 2024-09-29 12:38:26 +02: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
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
the results are written to standard output in human-readable [TAP
format](http://testanything.org/wiki/index.php/TAP_specification#THE_TAP_FORMAT).
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.
file. The file's test cases are run sequentially and in isolation. 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
✓ 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
ok 1 addition using bc
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
You can also invoke the `bats` interpreter with a path to a directory
containing multiple `.bats` files. Bats will run each test file
individually and aggregate the results. If any test case fails, `bats`
exits with a `1` status code.
You can invoke the `bats` interpreter with multiple test file
arguments, or with a path to a directory containing multiple `.bats`
files. Bats will run each test file individually and aggregate the
results. If any test case fails, `bats` exits with a `1` status code.
## Helpers and introspection
### The _run_ helper
If you're using Bats, you're probably most interested in testing a
command's exit status and output. Bats includes a `run` helper that
invokes its arguments as a command, saves the exit status and output
into special global variables, and then returns with a `0` status code
so you can continue to make assertions in your test case.
Many Bats tests need to run a command and then make assertions about
its exit status and output. Bats includes a `run` helper that invokes
its arguments as a command, saves the exit status and output into
special global variables, and then returns with a `0` status code so
you can continue to make assertions in your test case.
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
@ -138,7 +147,6 @@ Or you can skip conditionally:
```bash
@test "A test which should run" {
if [ foo != bar ]; then
skip "foo isn't bar"
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
There are several global variables you can use to introspect on Bats
@ -183,7 +197,7 @@ have permission to write to the installation prefix.
## Syntax Highlighting
* [Bats.tmbundle](https://github.com/drnic/Bats.tmbundle) from Dr Nic
* [Bats.tmbundle](https://github.com/drnic/Bats.tmbundle) from Dr Nic
Williams adds Bats syntax highlighting support for TextMate.
## Development

View File

@ -5,6 +5,27 @@ version() {
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() {
$(type -p greadlink readlink | head -1) "$1"
}
@ -35,26 +56,61 @@ BATS_LIBEXEC="$(abs_dirname "$0")"
export BATS_PREFIX="$(abs_dirname "$BATS_LIBEXEC")"
export PATH="$BATS_LIBEXEC:$PATH"
if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then
version
exit 0
fi
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
count_only=""
if [ "$1" = "-c" ]; then
count_only="-c"
shift
fi
unset count_flag pretty
[ -t 0 ] && [ -t 1 ] && pretty="1"
if [ -z "$1" ]; then
{ version
echo "usage: $0 [-c] <filename> [<filename> ...]"
} >&2
for option in "${options[@]}"; do
case "$option" in
"h" | "help" )
help
exit 0
;;
"v" | "version" )
version
exit 0
;;
"c" | "count" )
count_flag="-c"
;;
"t" | "tap" )
pretty=""
;;
"p" | "pretty" )
pretty="1"
;;
* )
usage >&2
exit 1
;;
esac
done
if [ "${#arguments[@]}" -eq 0 ]; then
usage >&2
exit 1
fi
filenames=()
for filename in "$@"; do
for filename in "${arguments[@]}"; do
if [ -d "$filename" ]; then
shopt -s nullglob
for suite_filename in "$(expand_path "$filename")"/*.bats; do
@ -67,7 +123,18 @@ for filename in "$@"; do
done
if [ "${#filenames[@]}" -eq 1 ]; then
exec bats-exec-test $count_only "${filenames[0]}"
command="bats-exec-test"
else
exec bats-exec-suite $count_only "${filenames[@]}"
command="bats-exec-suite"
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" {
run bats
[ $status -eq 1 ]
[ $(expr "${lines[1]}" : "usage:") -ne 0 ]
[ $(expr "${lines[1]}" : "Usage:") -ne 0 ]
}
@test "-v and --version print version number" {
@ -15,6 +15,12 @@ fixtures bats
[ $(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" {
run bats nonexistent
[ $status -eq 1 ]
@ -126,3 +132,15 @@ fixtures bats
[ "${lines[4]}" = "begin 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" ]
}