mirror of
https://github.com/sstephenson/bats.git
synced 2024-11-17 03:32:27 +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
|
||||
|
||||
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
|
||||
|
|
97
libexec/bats
97
libexec/bats
|
@ -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
|
||||
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
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
"c" | "count" )
|
||||
count_flag="-c"
|
||||
;;
|
||||
"t" | "tap" )
|
||||
pretty=""
|
||||
;;
|
||||
"p" | "pretty" )
|
||||
pretty="1"
|
||||
;;
|
||||
* )
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
count_only=""
|
||||
if [ "$1" = "-c" ]; then
|
||||
count_only="-c"
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
{ version
|
||||
echo "usage: $0 [-c] <filename> [<filename> ...]"
|
||||
} >&2
|
||||
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
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" {
|
||||
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" ]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user