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:
parent
b0606bc8cd
commit
a3229efbfa
56
README.md
56
README.md
|
@ -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
|
||||||
|
|
97
libexec/bats
97
libexec/bats
|
@ -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
154
libexec/bats-format-tap-stream
Executable 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
|
|
@ -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" ]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user