mirror of
https://github.com/zunit-zsh/zunit.git
synced 2026-09-10 06:36:15 -04:00
Separate source files, and add Makefile to compile
This commit is contained in:
parent
b210cafb68
commit
4f256c7d58
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,2 +1,6 @@
|
||||||
|
# Ignore test output, but keep the directory
|
||||||
/tests/_output/*
|
/tests/_output/*
|
||||||
!/tests/_output/.gitkeep
|
!/tests/_output/.gitkeep
|
||||||
|
|
||||||
|
# Ignore the compiled program
|
||||||
|
/zunit
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ before_script:
|
||||||
- chmod u+x .bin/{color,revolver,zvm}
|
- chmod u+x .bin/{color,revolver,zvm}
|
||||||
- export PATH="$HOME/.zvm/bin:$PWD/.bin:$PATH"
|
- export PATH="$HOME/.zvm/bin:$PWD/.bin:$PATH"
|
||||||
- zvm use ${ZVM_VERSION}
|
- zvm use ${ZVM_VERSION}
|
||||||
|
- make
|
||||||
script: ./zunit
|
script: ./zunit
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
|
|
|
||||||
12
Makefile
Normal file
12
Makefile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
target:
|
||||||
|
@cat /dev/null > zunit
|
||||||
|
@echo "#!/usr/bin/env zsh\n" >> zunit
|
||||||
|
@cat src/reports/*.zsh >> zunit
|
||||||
|
@cat $(filter-out src/zunit.zsh,$(wildcard src/*.zsh)) >> zunit
|
||||||
|
@cat src/commands/*.zsh >> zunit
|
||||||
|
@cat src/zunit.zsh >> zunit
|
||||||
|
@chmod u+x zunit
|
||||||
|
@echo "\033[0;32m✔\033[0;m ZUnit built successfully"
|
||||||
|
|
||||||
|
test:
|
||||||
|
@./zunit
|
||||||
341
src/assertions.zsh
Normal file
341
src/assertions.zsh
Normal file
|
|
@ -0,0 +1,341 @@
|
||||||
|
################################
|
||||||
|
# Internal assertion functions #
|
||||||
|
################################
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that two integers are equal
|
||||||
|
###
|
||||||
|
function _zunit_assert_equals() {
|
||||||
|
local value=$1 comparison=$2
|
||||||
|
|
||||||
|
[[ $value -eq $comparison ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is not equal to '$comparison'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that two integers are not equal
|
||||||
|
###
|
||||||
|
function _zunit_assert_not_equal_to() {
|
||||||
|
local value=$1 comparison=$2
|
||||||
|
|
||||||
|
[[ $value -ne $comparison ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is equal to '$comparison'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that two string are the same
|
||||||
|
###
|
||||||
|
function _zunit_assert_same_as() {
|
||||||
|
local value=$1 comparison=$2
|
||||||
|
|
||||||
|
[[ $value = $comparison ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is not the same as '$comparison'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that two string are different
|
||||||
|
###
|
||||||
|
function _zunit_assert_different_to() {
|
||||||
|
local value=$1 comparison=$2
|
||||||
|
|
||||||
|
[[ $value != $comparison ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is the same as '$comparison'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that a value is empty
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_empty() {
|
||||||
|
local value=$1
|
||||||
|
|
||||||
|
[[ -z ${value[@]} ]] && return 0
|
||||||
|
|
||||||
|
echo "'${value[@]}' is not empty"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that a value is not empty
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_not_empty() {
|
||||||
|
local value=$1
|
||||||
|
|
||||||
|
[[ -n ${value[@]} ]] && return 0
|
||||||
|
|
||||||
|
echo "value is empty"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that the value matches a regex pattern
|
||||||
|
###
|
||||||
|
function _zunit_assert_matches() {
|
||||||
|
local value=$1 pattern=$2
|
||||||
|
|
||||||
|
[[ $value =~ $pattern ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' does not match /$pattern/"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that the value does not match a regex pattern
|
||||||
|
###
|
||||||
|
function _zunit_assert_does_not_match() {
|
||||||
|
local value=$1 pattern=$2
|
||||||
|
|
||||||
|
[[ ! $value =~ $pattern ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' matches /$pattern/"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that a value is found in an array
|
||||||
|
###
|
||||||
|
function _zunit_assert_in() {
|
||||||
|
local i found=0 value=$1
|
||||||
|
local -a array
|
||||||
|
array=(${(@)@:2})
|
||||||
|
|
||||||
|
for i in ${(@f)array}; do
|
||||||
|
[[ $i = $value ]] && found=1
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
[[ $found -eq 1 ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is not in (${(@f)array})"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that a value is not found in an array
|
||||||
|
###
|
||||||
|
function _zunit_assert_not_in() {
|
||||||
|
local i found=0 value=$1
|
||||||
|
local -a array
|
||||||
|
array=(${(@)@:2})
|
||||||
|
|
||||||
|
for i in ${(@f)array}; do
|
||||||
|
[[ $i = $value ]] && found=1
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $found -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is in (${(@f)array})"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that a value is a key in a hash
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_key_in() {
|
||||||
|
local i found=0 value=$1
|
||||||
|
local -A hash
|
||||||
|
hash=(${(@)@:2})
|
||||||
|
|
||||||
|
for k v in ${(@kv)hash}; do
|
||||||
|
[[ $k = $value ]] && found=1
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $found -eq 1 ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is not a key in (${(@kv)hash})"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that a value is not a key in a hash
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_not_key_in() {
|
||||||
|
local i found=0 value=$1
|
||||||
|
local -A hash
|
||||||
|
hash=(${(@)@:2})
|
||||||
|
|
||||||
|
for k v in ${(@kv)hash}; do
|
||||||
|
[[ $k = $value ]] && found=1
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $found -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is a key in (${(@kv)hash})"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that a value is a value in a hash
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_value_in() {
|
||||||
|
local i found=0 value=$1
|
||||||
|
local -A hash
|
||||||
|
hash=(${(@)@:2})
|
||||||
|
|
||||||
|
for k v in ${(@kv)hash}; do
|
||||||
|
[[ $v = $value ]] && found=1
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $found -eq 1 ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is not a value in (${(@kv)hash})"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert that a value is not a value in a hash
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_not_value_in() {
|
||||||
|
local i found=0 value=$1
|
||||||
|
local -A hash
|
||||||
|
hash=(${(@)@:2})
|
||||||
|
|
||||||
|
for k v in ${(@kv)hash}; do
|
||||||
|
[[ $v = $value ]] && found=1
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $found -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
echo "'$value' is a value in (${(@kv)hash})"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert the a path exists
|
||||||
|
###
|
||||||
|
function _zunit_assert_exists() {
|
||||||
|
local pathname=$1 filepath
|
||||||
|
|
||||||
|
# If filepath is relative, prepend the test directory
|
||||||
|
if [[ "${pathname:0:1}" != "/" ]]; then
|
||||||
|
filepath="$testdir/${pathname}"
|
||||||
|
else
|
||||||
|
filepath="$pathname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -e "$filepath" ]] && return 0
|
||||||
|
|
||||||
|
echo "'$pathname' does not exist"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert the a path exists and is a file
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_file() {
|
||||||
|
local pathname=$1 filepath
|
||||||
|
|
||||||
|
# If filepath is relative, prepend the test directory
|
||||||
|
if [[ "${pathname:0:1}" != "/" ]]; then
|
||||||
|
filepath="$testdir/${pathname}"
|
||||||
|
else
|
||||||
|
filepath="$pathname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$filepath" ]] && return 0
|
||||||
|
|
||||||
|
echo "'$pathname' does not exist or is not a file"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert the a path exists and is a directory
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_dir() {
|
||||||
|
local pathname=$1 filepath
|
||||||
|
|
||||||
|
# If filepath is relative, prepend the test directory
|
||||||
|
if [[ "${pathname:0:1}" != "/" ]]; then
|
||||||
|
filepath="$testdir/$pathname"
|
||||||
|
else
|
||||||
|
filepath="$pathname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -d "$filepath" ]] && return 0
|
||||||
|
|
||||||
|
echo "'$pathname' does not exist or is not a directory"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert the a path exists and is a symbolic link
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_link() {
|
||||||
|
local pathname=$1 filepath
|
||||||
|
|
||||||
|
# If filepath is relative, prepend the test directory
|
||||||
|
if [[ "${pathname:0:1}" != "/" ]]; then
|
||||||
|
filepath="$testdir/${pathname}"
|
||||||
|
else
|
||||||
|
filepath="$pathname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -h "$filepath" ]] && return 0
|
||||||
|
|
||||||
|
echo "'$pathname' does not exist or is not a symbolic link"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert the a path exists and is readable
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_readable() {
|
||||||
|
local pathname=$1 filepath
|
||||||
|
|
||||||
|
# If filepath is relative, prepend the test directory
|
||||||
|
if [[ "${pathname:0:1}" != "/" ]]; then
|
||||||
|
filepath="$testdir/${pathname}"
|
||||||
|
else
|
||||||
|
filepath="$pathname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -r "$filepath" ]] && return 0
|
||||||
|
|
||||||
|
echo "'$pathname' does not exist or is not readable"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert the a path exists and is writable
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_writable() {
|
||||||
|
local pathname=$1 filepath
|
||||||
|
|
||||||
|
# If filepath is relative, prepend the test directory
|
||||||
|
if [[ "${pathname:0:1}" != "/" ]]; then
|
||||||
|
filepath="$testdir/${pathname}"
|
||||||
|
else
|
||||||
|
filepath="$pathname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -w "$filepath" ]] && return 0
|
||||||
|
|
||||||
|
echo "'$pathname' does not exist or is not writable"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Assert the a path exists and is executable
|
||||||
|
###
|
||||||
|
function _zunit_assert_is_executable() {
|
||||||
|
local pathname=$1 filepath
|
||||||
|
|
||||||
|
# If filepath is relative, prepend the test directory
|
||||||
|
if [[ "${pathname:0:1}" != "/" ]]; then
|
||||||
|
filepath="$testdir/${pathname}"
|
||||||
|
else
|
||||||
|
filepath="$pathname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -x "$filepath" ]] && return 0
|
||||||
|
|
||||||
|
echo "'$pathname' does not exist or is not executable"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
101
src/commands/init.zsh
Normal file
101
src/commands/init.zsh
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
############################
|
||||||
|
# The 'zunit init' command #
|
||||||
|
############################
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output usage information and exit
|
||||||
|
###
|
||||||
|
function _zunit_init_usage() {
|
||||||
|
echo "$(color yellow 'Usage:')"
|
||||||
|
echo " zunit init [options]"
|
||||||
|
echo
|
||||||
|
echo "$(color yellow 'Options:')"
|
||||||
|
echo " -h, --help Output help text and exit"
|
||||||
|
echo " -v, --version Output version information and exit"
|
||||||
|
echo " -t, --travis Generate .travis.yml in project"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Parse a YAML config file
|
||||||
|
# Based on https://gist.github.com/pkuczynski/8665367
|
||||||
|
###
|
||||||
|
function _zunit_parse_yaml() {
|
||||||
|
local s w fs prefix=$2
|
||||||
|
s='[[:space:]]*'
|
||||||
|
w='[a-zA-Z0-9_]*'
|
||||||
|
fs="$(echo @|tr @ '\034')"
|
||||||
|
sed -ne "s|^\(${s}\)\(${w}\)${s}:${s}\"\(.*\)\"${s}\$|\1${fs}\2${fs}\3|p" \
|
||||||
|
-e "s|^\(${s}\)\(${w}\)${s}[:-]${s}\(.*\)${s}\$|\1${fs}\2${fs}\3|p" "$1" |
|
||||||
|
awk -F"${fs}" '{
|
||||||
|
indent = length($1)/2;
|
||||||
|
vname[indent] = $2;
|
||||||
|
for (i in vname) {if (i > indent) {delete vname[i]}}
|
||||||
|
if (length($3) > 0) {
|
||||||
|
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
|
||||||
|
printf("%s%s%s=(\"%s\")\n", "'"$prefix"'",vn, $2, $3);
|
||||||
|
}
|
||||||
|
}' | sed 's/_=/+=/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
function _zunit_init() {
|
||||||
|
local with_travis
|
||||||
|
|
||||||
|
zparseopts -D t=with_travis -travis=with_travis
|
||||||
|
|
||||||
|
local yaml="tap: false
|
||||||
|
directories:
|
||||||
|
tests: tests
|
||||||
|
output: tests/_output
|
||||||
|
support: tests/_support"
|
||||||
|
|
||||||
|
local example="#!/usr/bin/env zunit
|
||||||
|
|
||||||
|
@test 'Example' {
|
||||||
|
assert "'"true"'" same_as "'"false"'"
|
||||||
|
}"
|
||||||
|
|
||||||
|
local bootstrap="#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
# Write your bootstrap code here"
|
||||||
|
|
||||||
|
local travis_yml='addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
zsh
|
||||||
|
before_script:
|
||||||
|
- mkdir .bin
|
||||||
|
- curl -L https://raw.githubusercontent.com/molovo/revolver/master/revolver > .bin/revolver
|
||||||
|
- curl -L https://raw.githubusercontent.com/molovo/color/master/color.zsh > .bin/color
|
||||||
|
- curl -L https://raw.githubusercontent.com/molovo/zunit/master/zunit > .bin/zunit
|
||||||
|
- chmod u+x .bin/{color,revolver,zunit}
|
||||||
|
- export PATH="$PWD/.bin:$PATH"
|
||||||
|
script: zunit'
|
||||||
|
|
||||||
|
if [[ -f "$PWD/.zunit.yml" ]]; then
|
||||||
|
echo $(color red "Zunit config file already exists at $PWD/.zunit.yml") >&2
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "$yaml" > "$PWD/.zunit.yml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$PWD/tests" ]]; then
|
||||||
|
echo $(color red "Directory already exists at $PWD/tests") >&2
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
mkdir -p tests/_{output,support}
|
||||||
|
touch tests/_{output,support}/.gitkeep
|
||||||
|
|
||||||
|
echo "$bootstrap" > "$PWD/tests/_support/bootstrap"
|
||||||
|
echo "$example" > "$PWD/tests/example.zunit"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#statements
|
||||||
|
if [[ -n $with_travis ]]; then
|
||||||
|
if [[ -f "$PWD/.travis.yml" ]]; then
|
||||||
|
echo $(color red "Travis config already exists at $PWD/.travis.yml") >&2
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "$travis_yml" > "$PWD/.travis.yml"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
481
src/commands/run.zsh
Normal file
481
src/commands/run.zsh
Normal file
|
|
@ -0,0 +1,481 @@
|
||||||
|
###########################
|
||||||
|
# The 'zunit run' command #
|
||||||
|
###########################
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output usage information and exit
|
||||||
|
###
|
||||||
|
function _zunit_run_usage() {
|
||||||
|
echo "$(color yellow 'Usage:')"
|
||||||
|
echo " zunit run [options] [tests...]"
|
||||||
|
echo
|
||||||
|
echo "$(color yellow 'Options:')"
|
||||||
|
echo " -h, --help Output help text and exit"
|
||||||
|
echo " -v, --version Output version information and exit"
|
||||||
|
echo " -f, --fail-fast Stop the test runner immediately after the first failure"
|
||||||
|
echo " -t, --tap Output results in a TAP compatible format"
|
||||||
|
echo " --output-text Print results to a text log, in TAP compatible format"
|
||||||
|
echo " --output-html Print results to a HTML page"
|
||||||
|
echo " --allow-risky Supress warnings generated for risky tests"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Format a ms timestamp in a human-readable format
|
||||||
|
###
|
||||||
|
function _zunit_human_time() {
|
||||||
|
local ms=$1
|
||||||
|
local tmp=$(( $1 / 1000 ))
|
||||||
|
local days=$(( tmp / 60 / 60 / 24 ))
|
||||||
|
local hours=$(( tmp / 60 / 60 % 24 ))
|
||||||
|
local minutes=$(( tmp / 60 % 60 ))
|
||||||
|
local seconds=$(( tmp % 60 ))
|
||||||
|
(( $days > 0 )) && print -n "${days}d "
|
||||||
|
(( $hours > 0 )) && print -n "${hours}h "
|
||||||
|
(( $minutes > 0 )) && print -n "${minutes}m "
|
||||||
|
(( $seconds > 5 )) && print -n "${seconds}s "
|
||||||
|
(( $seconds < 30 )) && (( $seconds > 5 )) && print -n "$(( ms - $((seconds*1000)) ))ms"
|
||||||
|
(( $tmp <= 5 )) && print -n "${1}ms"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output test results
|
||||||
|
###
|
||||||
|
function _zunit_output_results() {
|
||||||
|
integer elapsed=$(( end_time - start_time ))
|
||||||
|
echo
|
||||||
|
echo "$total tests run in $(_zunit_human_time $elapsed)"
|
||||||
|
echo
|
||||||
|
echo "$(color yellow underline 'Results') "
|
||||||
|
echo "$(color green '✔') Passed $passed "
|
||||||
|
echo "$(color red '✘') Failed $failed "
|
||||||
|
echo "$(color red '‼') Errors $errors "
|
||||||
|
echo "$(color magenta '•') Skipped $skipped "
|
||||||
|
echo "$(color yellow '‼') Warnings $warnings "
|
||||||
|
echo
|
||||||
|
|
||||||
|
[[ -n $output_text ]] && echo "TAP report written at $PWD/$logfile_text"
|
||||||
|
[[ -n $output_html ]] && echo "HTML report written at $PWD/$logfile_html"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Execute a test and store the result
|
||||||
|
###
|
||||||
|
function _zunit_execute_test() {
|
||||||
|
local name="$1" body="$2"
|
||||||
|
|
||||||
|
if [[ -n $body ]] && [[ -n $name ]]; then
|
||||||
|
# Update the progress indicator
|
||||||
|
[[ -z $tap ]] && revolver update "${name}"
|
||||||
|
|
||||||
|
# Make sure we don't already have a function defined
|
||||||
|
(( $+functions[__zunit_tmp_test_function] )) && \
|
||||||
|
unfunction __zunit_tmp_test_function
|
||||||
|
|
||||||
|
func="function __zunit_tmp_test_function() {
|
||||||
|
setopt ERR_EXIT
|
||||||
|
integer state
|
||||||
|
integer _zunit_assertion_count=0
|
||||||
|
local output
|
||||||
|
typeset -a lines
|
||||||
|
|
||||||
|
if (( $+functions[__zunit_test_setup] )); then
|
||||||
|
__zunit_test_setup >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
${body}
|
||||||
|
|
||||||
|
if (( $+functions[__zunit_test_teardown] )); then
|
||||||
|
__zunit_test_teardown >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ \$_zunit_assertion_count -gt 0 ]] || return 248
|
||||||
|
}"
|
||||||
|
|
||||||
|
# Quietly eval the body into a variable as a first test
|
||||||
|
output=$(eval "$(echo "$func")" 2>&1)
|
||||||
|
|
||||||
|
total=$(( total + 1 ))
|
||||||
|
|
||||||
|
# Check the status of the eval, and output any errors
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
_zunit_error 'Failed to parse test body' $output
|
||||||
|
|
||||||
|
return 126
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run the eval again, this time within the current context so that
|
||||||
|
# the function is registered in the current scope
|
||||||
|
eval "$(echo "$func")" 2>/dev/null
|
||||||
|
|
||||||
|
# Any errors should have been caught above, but if the function
|
||||||
|
# does not exist, we can't go any further
|
||||||
|
if (( ! $+functions[__zunit_tmp_test_function] )); then
|
||||||
|
_zunit_error 'Failed to parse test body'
|
||||||
|
|
||||||
|
return 126
|
||||||
|
fi
|
||||||
|
|
||||||
|
autoload is-at-least
|
||||||
|
|
||||||
|
# Check if a time limit has been specified
|
||||||
|
if is-at-least 5.1.0 && [[ -n $zunit_config_time_limit ]]; then
|
||||||
|
# Create a wrapper function around the test
|
||||||
|
__zunit_async_test_wrapper() {
|
||||||
|
local pid
|
||||||
|
|
||||||
|
# Get the current timestamp, and the time limit, and use those to
|
||||||
|
# work out the kill time for the sub process
|
||||||
|
integer time_limit=$(( ${zunit_config_time_limit:-30} * 1000 ))
|
||||||
|
integer time=$(( EPOCHREALTIME * 1000 ))
|
||||||
|
integer kill_time=$(( $time + $time_limit ))
|
||||||
|
|
||||||
|
# Launch the test function asynchronously and store its PID
|
||||||
|
__zunit_tmp_test_function &
|
||||||
|
pid=$!
|
||||||
|
|
||||||
|
# While the child process is still running
|
||||||
|
while kill -0 $pid >/dev/null 2>&1; do
|
||||||
|
# Check that the kill time has not yet been reached
|
||||||
|
time=$(( EPOCHREALTIME * 1000 ))
|
||||||
|
if [[ $time -gt $kill_time ]]; then
|
||||||
|
# The kill time has been reached, kill the child process,
|
||||||
|
# and exit the wrapper function
|
||||||
|
kill -9 $pid >/dev/null 2>&1
|
||||||
|
exit 78
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Use wait to get the exit code from the background process,
|
||||||
|
# and return that so that the test result can be deduced
|
||||||
|
wait $pid
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
# Launch the async wrapper, and capture the output in a variable
|
||||||
|
output="$(__zunit_async_test_wrapper 2>&1)"
|
||||||
|
else
|
||||||
|
# Launch the test, and capture the output in a variable
|
||||||
|
output="$(__zunit_tmp_test_function 2>&1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output the result to the user
|
||||||
|
state=$?
|
||||||
|
if [[ $state -eq 48 ]]; then
|
||||||
|
_zunit_skip $output
|
||||||
|
|
||||||
|
return
|
||||||
|
elif [[ $state -eq 78 ]]; then
|
||||||
|
_zunit_error "Test took too long to run. Terminated after ${zunit_config_time_limit:-30} seconds" $output
|
||||||
|
|
||||||
|
return
|
||||||
|
elif [[ -z $allow_risky && $state -eq 248 ]]; then
|
||||||
|
_zunit_warn 'No assertions were run, test is risky'
|
||||||
|
|
||||||
|
return
|
||||||
|
elif [[ -n $allow_risky && $state -eq 248 ]] || [[ $state -eq 0 ]]; then
|
||||||
|
_zunit_success
|
||||||
|
|
||||||
|
return
|
||||||
|
else
|
||||||
|
_zunit_failure $output
|
||||||
|
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Encode test name into a value which can be used as a hash key
|
||||||
|
###
|
||||||
|
function _zunit_encode_test_name() {
|
||||||
|
echo "$1" | tr A-Z a-z \
|
||||||
|
| tr _ ' ' \
|
||||||
|
| tr - ' ' \
|
||||||
|
| tr -s ' ' \
|
||||||
|
| sed 's/\- /-/' \
|
||||||
|
| sed 's/ \-/-/' \
|
||||||
|
| tr ' ' "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Run all tests within a file
|
||||||
|
###
|
||||||
|
function _zunit_run_testfile() {
|
||||||
|
local testbody testname pattern \
|
||||||
|
setup teardown \
|
||||||
|
testfile="$1" testdir="$(dirname "$testfile")"
|
||||||
|
local -a lines tests test_names
|
||||||
|
tests=()
|
||||||
|
test_names=()
|
||||||
|
|
||||||
|
# Update status message
|
||||||
|
[[ -z $tap ]] && revolver update "Loading tests from $testfile"
|
||||||
|
|
||||||
|
# A regex pattern to match test declarations
|
||||||
|
pattern='^ *@test *([^ ].*) *\{ *(.*)$'
|
||||||
|
|
||||||
|
# Loop through each of the lines in the file
|
||||||
|
local oldIFS=$IFS
|
||||||
|
IFS=$'\n' lines=($(cat $testfile))
|
||||||
|
IFS=$oldIFS
|
||||||
|
for line in $lines[@]; do
|
||||||
|
# Match current line against pattern
|
||||||
|
if [[ "$line" =~ $pattern ]]; then
|
||||||
|
# Get test name from matches
|
||||||
|
testname="${line[(( ${line[(i)[\']]}+1 )),(( ${line[(I)[\']]}-1 ))]}"
|
||||||
|
test_names=($test_names $testname)
|
||||||
|
tests[${#test_names}]=''
|
||||||
|
elif [[ "$line" =~ '^@setup([ ])?\{$' ]]; then
|
||||||
|
setup=''
|
||||||
|
parsing_setup=true
|
||||||
|
elif [[ "$line" =~ '^@teardown([ ])?\{$' ]]; then
|
||||||
|
teardown=''
|
||||||
|
parsing_teardown=true
|
||||||
|
elif [[ "$line" = '}' ]]; then
|
||||||
|
testname=''
|
||||||
|
parsing_setup=''
|
||||||
|
parsing_teardown=''
|
||||||
|
else
|
||||||
|
if [[ -n $testname ]]; then
|
||||||
|
tests[${#test_names}]+="$line"$'\n'
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $parsing_setup ]]; then
|
||||||
|
setup+="$line"$'\n'
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $parsing_teardown ]]; then
|
||||||
|
teardown+="$line"$'\n'
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n $setup ]]; then
|
||||||
|
setupfunc="function __zunit_test_setup() {
|
||||||
|
${setup}
|
||||||
|
}"
|
||||||
|
|
||||||
|
# Quietly eval the body into a variable as a first test
|
||||||
|
output=$(eval "$(echo "$setupfunc")" 2>&1)
|
||||||
|
|
||||||
|
# Check the status of the eval, and output any errors
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
_zunit_error "Failed to parse setup method" $output
|
||||||
|
|
||||||
|
return 126
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run the eval again, this time within the current context so that
|
||||||
|
# the function is registered in the current scope
|
||||||
|
eval "$(echo "$setupfunc")" 2>/dev/null
|
||||||
|
|
||||||
|
# Any errors should have been caught above, but if the function
|
||||||
|
# does not exist, we can't go any further
|
||||||
|
if (( ! $+functions[__zunit_test_setup] )); then
|
||||||
|
_zunit_error "Failed to parse setup method"
|
||||||
|
|
||||||
|
return 126
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $teardown ]]; then
|
||||||
|
teardownfunc="function __zunit_test_teardown() {
|
||||||
|
${teardown}
|
||||||
|
}"
|
||||||
|
|
||||||
|
# Quietly eval the body into a variable as a first test
|
||||||
|
output=$(eval "$(echo "$teardownfunc")" 2>&1)
|
||||||
|
|
||||||
|
# Check the status of the eval, and output any errors
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
_zunit_error "Failed to parse teardown method" $output
|
||||||
|
|
||||||
|
return 126
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run the eval again, this time within the current context so that
|
||||||
|
# the function is registered in the current scope
|
||||||
|
eval "$(echo "$teardownfunc")" 2>/dev/null
|
||||||
|
|
||||||
|
# Any errors should have been caught above, but if the function
|
||||||
|
# does not exist, we can't go any further
|
||||||
|
if (( ! $+functions[__zunit_test_teardown] )); then
|
||||||
|
_zunit_error "Failed to parse teardown method"
|
||||||
|
|
||||||
|
return 126
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Loop through each of the tests and execute it
|
||||||
|
integer i=1
|
||||||
|
local name body
|
||||||
|
for name in "${test_names[@]}"; do
|
||||||
|
body="${tests[$i]}"
|
||||||
|
_zunit_execute_test "$name" "$body"
|
||||||
|
i=$(( i + 1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
(( $+functions[__zunit_test_setup] )) && unfunction __zunit_test_setup
|
||||||
|
(( $+functions[__zunit_test_teardown] )) && unfunction __zunit_test_teardown
|
||||||
|
(( $+functions[__zunit_tmp_test_function] )) && unfunction __zunit_tmp_test_function
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Parse a list of arguments
|
||||||
|
###
|
||||||
|
function _zunit_parse_argument() {
|
||||||
|
local argument="$1"
|
||||||
|
|
||||||
|
# If the argument begins with an underscore, then it
|
||||||
|
# should not be run, so we skip it
|
||||||
|
if [[ "${argument:0:1}" = "_" || "$(basename $argument | cut -c 1)" = "_" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If the argument is a directory
|
||||||
|
if [[ -d $argument ]]; then
|
||||||
|
# Loop through each of the files in the directory
|
||||||
|
for file in $(find $argument -mindepth 1 -maxdepth 1); do
|
||||||
|
# Run it through the parser again
|
||||||
|
_zunit_parse_argument $file
|
||||||
|
done
|
||||||
|
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If it is a valid file
|
||||||
|
if [[ -f $argument ]]; then
|
||||||
|
# Grab the first line of the file
|
||||||
|
line=$(cat $argument | head -n 1)
|
||||||
|
|
||||||
|
# Check for the zunit shebang
|
||||||
|
if [[ $line = "#!/usr/bin/env zunit" ]]; then
|
||||||
|
# Add it to the array
|
||||||
|
testfiles[(( ${#testfiles} + 1 ))]=($argument)
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The test file does not contain the zunit shebang, therefore
|
||||||
|
# we can't trust that running it will not be harmful, and throw
|
||||||
|
# a fatal error
|
||||||
|
echo $(color red "File '$argument' is not a valid zunit test file") >&2
|
||||||
|
echo "Test files must contain the following shebang on the first line" >&2
|
||||||
|
echo " #!/usr/bin/env zunit" >&2
|
||||||
|
exit 126
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The file could not be found, so we throw a fatal error
|
||||||
|
echo $(color red "Test file or directory '$argument' could not be found") >&2
|
||||||
|
exit 126
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Run tests
|
||||||
|
###
|
||||||
|
function _zunit_run() {
|
||||||
|
local -a arguments testfiles
|
||||||
|
local fail_fast tap allow_risky
|
||||||
|
local output_text logfile_text output_html logfile_html
|
||||||
|
|
||||||
|
zmodload zsh/datetime
|
||||||
|
local start_time=$((EPOCHREALTIME*1000)) end_time
|
||||||
|
|
||||||
|
zparseopts -D -E \
|
||||||
|
h=help -help=help \
|
||||||
|
v=version -version=version \
|
||||||
|
f=fail_fast -fail-fast=fail_fast \
|
||||||
|
t=tap -tap=tap \
|
||||||
|
-output-text=output_text \
|
||||||
|
-output-html=output_html \
|
||||||
|
-allow-risky=allow_risky
|
||||||
|
|
||||||
|
if [[ -n $tap ]] || [[ "$zunit_config_tap" = "true" ]]; then
|
||||||
|
tap=1
|
||||||
|
echo 'TAP version 13'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $tap ]]; then
|
||||||
|
# Print version information
|
||||||
|
echo $(color yellow 'Launching ZUnit')
|
||||||
|
echo "ZUnit: $(_zunit_version)"
|
||||||
|
echo "ZSH: $(zsh --version)"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $output_text ]]; then
|
||||||
|
if [[ $missing_config -eq 1 ]]; then
|
||||||
|
echo $(color red '.zunit.yml could not be found. Run `zulu init`')
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $zunit_config_directories_output ]]; then
|
||||||
|
echo $(color red 'Output directory must be specified in .zunit.yml')
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $output_text ]]; then
|
||||||
|
logfile_text="$zunit_config_directories_output/output.txt"
|
||||||
|
echo 'TAP version 13' > $logfile_text
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $output_html ]]; then
|
||||||
|
logfile_html="$zunit_config_directories_output/output.html"
|
||||||
|
_zunit_html_header > $logfile_html
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $zunit_config_directories_support ]]; then
|
||||||
|
local support="$zunit_config_directories_support"
|
||||||
|
if [[ ! -d $support ]]; then
|
||||||
|
echo $(color red "Support directory at $support is missing")
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$support/bootstrap" ]]; then
|
||||||
|
source "$support/bootstrap"
|
||||||
|
echo "$(color green '✔') Sourced bootstrap script $support/bootstrap"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
arguments=("$@")
|
||||||
|
testfiles=()
|
||||||
|
|
||||||
|
# Start the progress indicator
|
||||||
|
[[ -z $tap ]] && revolver start 'Loading tests'
|
||||||
|
|
||||||
|
# If no arguments are passed, use the current directory
|
||||||
|
if [[ ${#arguments} -eq 0 ]]; then
|
||||||
|
if [[ -n $zunit_config_directories_tests ]]; then
|
||||||
|
arguments=("$zunit_config_directories_tests")
|
||||||
|
else
|
||||||
|
arguments=("tests")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Loop through each of the passed arguments
|
||||||
|
local argument
|
||||||
|
for argument in $arguments; do
|
||||||
|
# Parse the argument, so that we end up with a list of valid files
|
||||||
|
_zunit_parse_argument $argument
|
||||||
|
done
|
||||||
|
|
||||||
|
# Loop through each of the test files and run them
|
||||||
|
local line
|
||||||
|
local total=0 passed=0 failed=0 errors=0 warnings=0 skipped=0
|
||||||
|
for testfile in $testfiles; do
|
||||||
|
_zunit_run_testfile $testfile
|
||||||
|
done
|
||||||
|
|
||||||
|
end_time=$((EPOCHREALTIME*1000))
|
||||||
|
|
||||||
|
[[ -n $tap ]] && echo "1..$total"
|
||||||
|
[[ -n $output_text ]] && echo "1..$total" >> $logfile_text
|
||||||
|
[[ -n $output_html ]] && _zunit_html_footer >> $logfile_html
|
||||||
|
|
||||||
|
[[ -z $tap ]] && _zunit_output_results && revolver stop
|
||||||
|
|
||||||
|
[[ $(( $passed + $skipped )) -eq $total ]]
|
||||||
|
}
|
||||||
127
src/events.zsh
Normal file
127
src/events.zsh
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
##########################################
|
||||||
|
# Functions for handling internal events #
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
###
|
||||||
|
# Shutdown testing early
|
||||||
|
# Usually called if --fail-fast is specified
|
||||||
|
###
|
||||||
|
function _zunit_fail_shutdown() {
|
||||||
|
# Kill the revolver process
|
||||||
|
[[ -z $tap ]] && revolver stop
|
||||||
|
|
||||||
|
echo $(color red bold 'Execution halted after failure')
|
||||||
|
end_time=$((EPOCHREALTIME*1000))
|
||||||
|
|
||||||
|
[[ -z $tap ]] && _zunit_output_results
|
||||||
|
|
||||||
|
# Print end of HTML report
|
||||||
|
if [[ -n $output_html ]]; then
|
||||||
|
name='Execution halted after failure'
|
||||||
|
_zunit_html_error >> $logfile_html
|
||||||
|
_zunit_html_footer >> $logfile_html
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a success message
|
||||||
|
###
|
||||||
|
function _zunit_success() {
|
||||||
|
[[ -n $output_text ]] && _zunit_tap_success "$@" >> $logfile_text
|
||||||
|
[[ -n $output_html ]] && _zunit_html_success "$@" >> $logfile_html
|
||||||
|
|
||||||
|
passed=$(( passed + 1 ))
|
||||||
|
|
||||||
|
if [[ -n $tap ]]; then
|
||||||
|
_zunit_tap_success "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(color green '✔') ${name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a failure message
|
||||||
|
###
|
||||||
|
function _zunit_failure() {
|
||||||
|
local message="$1" output="${(@)@:2}"
|
||||||
|
|
||||||
|
failed=$(( failed + 1 ))
|
||||||
|
|
||||||
|
[[ -n $output_text ]] && _zunit_tap_failure "$@" >> $logfile_text
|
||||||
|
[[ -n $output_html ]] && _zunit_html_failure "$@" >> $logfile_html
|
||||||
|
|
||||||
|
if [[ -n $tap ]]; then
|
||||||
|
_zunit_tap_failure "$@"
|
||||||
|
else
|
||||||
|
echo "$(color red '✘' ${name})"
|
||||||
|
echo " $(color red underline ${message})"
|
||||||
|
echo " $(color red ${output})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n $fail_fast ]] && _zunit_fail_shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a error message
|
||||||
|
###
|
||||||
|
function _zunit_error() {
|
||||||
|
local message="$1" output="${(@)@:2}"
|
||||||
|
|
||||||
|
errors=$(( errors + 1 ))
|
||||||
|
|
||||||
|
[[ -n $output_text ]] && _zunit_tap_error "$@" >> $logfile_text
|
||||||
|
[[ -n $output_html ]] && _zunit_html_error "$@" >> $logfile_html
|
||||||
|
|
||||||
|
if [[ -n $tap ]]; then
|
||||||
|
_zunit_tap_error "$@"
|
||||||
|
else
|
||||||
|
echo "$(color red '‼' ${name})"
|
||||||
|
echo " $(color red underline ${message})"
|
||||||
|
echo " $(color red ${output})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n $fail_fast ]] && _zunit_fail_shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a warning message
|
||||||
|
###
|
||||||
|
function _zunit_warn() {
|
||||||
|
local message="$@"
|
||||||
|
|
||||||
|
warnings=$(( warnings + 1 ))
|
||||||
|
|
||||||
|
[[ -n $output_text ]] && _zunit_tap_warn "$@" >> $logfile_text
|
||||||
|
[[ -n $output_html ]] && _zunit_html_warn "$@" >> $logfile_html
|
||||||
|
|
||||||
|
if [[ -n $tap ]]; then
|
||||||
|
_zunit_tap_warn "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(color yellow '‼') ${name}"
|
||||||
|
echo " $(color yellow underline ${message})"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a skipped test message
|
||||||
|
###
|
||||||
|
function _zunit_skip() {
|
||||||
|
local message="$@"
|
||||||
|
|
||||||
|
skipped=$(( skipped + 1 ))
|
||||||
|
|
||||||
|
[[ -n $output_text ]] && _zunit_tap_skip "$@" >> $logfile_text
|
||||||
|
[[ -n $output_html ]] && _zunit_html_skip "$@" >> $logfile_html
|
||||||
|
|
||||||
|
if [[ -n $tap ]]; then
|
||||||
|
_zunit_tap_skip "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(color magenta '•') Skipped: ${name}"
|
||||||
|
echo " # ${message}"
|
||||||
|
}
|
||||||
122
src/helpers.zsh
Normal file
122
src/helpers.zsh
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
################################
|
||||||
|
# Helpers for use within tests #
|
||||||
|
################################
|
||||||
|
|
||||||
|
###
|
||||||
|
# Find a file, and load it into the environment
|
||||||
|
###
|
||||||
|
function load() {
|
||||||
|
local name="$1"
|
||||||
|
local filename
|
||||||
|
|
||||||
|
# If filepath is absolute, then use it as is
|
||||||
|
if [[ "${name:0:1}" = "/" ]]; then
|
||||||
|
filename="${name}"
|
||||||
|
# If it's relative, prepend the test directory
|
||||||
|
else
|
||||||
|
filename="$testdir/${name}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if the file exists
|
||||||
|
if [[ -f "$filename" ]]; then
|
||||||
|
# Source the file and exit if it's found
|
||||||
|
source "$filename"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Perform the check again, adding the .zsh extension
|
||||||
|
if [[ -f "$filename.zsh" ]]; then
|
||||||
|
# Source the file and exit if it's found
|
||||||
|
source "$filename.zsh"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output an error message to the user
|
||||||
|
echo "File $filename does not exist" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Run an external command and capture its output and exit status
|
||||||
|
###
|
||||||
|
function run() {
|
||||||
|
# Stop the shell from exiting on error temporarily
|
||||||
|
unsetopt ERR_EXIT
|
||||||
|
|
||||||
|
# Preserve current $IFS
|
||||||
|
local oldIFS=$IFS name
|
||||||
|
local -a cmd lines
|
||||||
|
|
||||||
|
# Grab the first argument
|
||||||
|
cmd=(${@[@]})
|
||||||
|
name="${cmd[1]}"
|
||||||
|
|
||||||
|
# If the command is not an existing command or file,
|
||||||
|
# then prepend the test directory to the path
|
||||||
|
type $name > /dev/null
|
||||||
|
if [[ $? -ne 0 && ! -f $name && -f "$testdir/${name}" ]]; then
|
||||||
|
cmd[1]="$testdir/${name}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Store full output in a variable
|
||||||
|
output=$("${cmd[@]}" 2>&1)
|
||||||
|
|
||||||
|
# Get the process exit state
|
||||||
|
state="$?"
|
||||||
|
|
||||||
|
# Store individual lines of output in an array
|
||||||
|
IFS=$'\n'
|
||||||
|
lines=($output)
|
||||||
|
|
||||||
|
# Restore $IFS
|
||||||
|
IFS=$oldIFS
|
||||||
|
|
||||||
|
# Restore the exit on error state
|
||||||
|
setopt ERR_EXIT
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Redirect the assertion shorthand to the correct function
|
||||||
|
###
|
||||||
|
function assert() {
|
||||||
|
local value=$1 assertion=$2
|
||||||
|
local -a comparisons
|
||||||
|
|
||||||
|
IFS=$'\n'
|
||||||
|
|
||||||
|
comparisons=(${(@)@:3})
|
||||||
|
|
||||||
|
if [[ -z $assertion ]]; then
|
||||||
|
assertion=$value
|
||||||
|
value=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( ! $+functions[_zunit_assert_${assertion}] )); then
|
||||||
|
echo "$(color red "Assertion $assertion does not exist")"
|
||||||
|
exit 127
|
||||||
|
fi
|
||||||
|
|
||||||
|
_zunit_assertion_count=$(( _zunit_assertion_count + 1 ))
|
||||||
|
|
||||||
|
"_zunit_assert_${assertion}" $value ${(@f)comparisons[@]}
|
||||||
|
|
||||||
|
local state=$?
|
||||||
|
|
||||||
|
if [[ $state -ne 0 ]]; then
|
||||||
|
exit $state
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS=$oldIFS
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Mark the current test as skipped
|
||||||
|
###
|
||||||
|
function skip() {
|
||||||
|
# Exit code 48 will skip the test, so all we have to do
|
||||||
|
# to mark the test as skipped is exit.
|
||||||
|
# The reason for skipping is echoed to stdout first, so that
|
||||||
|
# it can be picked up by the error handler
|
||||||
|
echo "$@"
|
||||||
|
exit 48
|
||||||
|
}
|
||||||
72
src/reports/html.zsh
Normal file
72
src/reports/html.zsh
Normal file
File diff suppressed because one or more lines are too long
66
src/reports/tap.zsh
Normal file
66
src/reports/tap.zsh
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
########################################
|
||||||
|
# Functions for handling TAP reporting #
|
||||||
|
########################################
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a TAP compatible success message
|
||||||
|
###
|
||||||
|
function _zunit_tap_success() {
|
||||||
|
echo "ok ${total} - ${name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a TAP compatible failure message
|
||||||
|
###
|
||||||
|
function _zunit_tap_failure() {
|
||||||
|
local message="$@"
|
||||||
|
|
||||||
|
echo "not ok ${total} - Failure: ${name}"
|
||||||
|
echo " ---"
|
||||||
|
echo " message: ${message}"
|
||||||
|
echo " severity: fail"
|
||||||
|
echo " ..."
|
||||||
|
|
||||||
|
[[ -n $fail_fast ]] && echo "Bail out!"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a TAP compatible error message
|
||||||
|
###
|
||||||
|
function _zunit_tap_error() {
|
||||||
|
local message="$@"
|
||||||
|
|
||||||
|
echo "not ok ${total} - Error: ${name}"
|
||||||
|
echo " ---"
|
||||||
|
echo " message: ${message}"
|
||||||
|
echo " severity: fail"
|
||||||
|
echo " ..."
|
||||||
|
|
||||||
|
[[ -n $fail_fast ]] && echo "Bail out!"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a TAP compatible warning message
|
||||||
|
###
|
||||||
|
function _zunit_tap_warn() {
|
||||||
|
local message="$@"
|
||||||
|
|
||||||
|
echo "ok ${total} - Warning: ${name}"
|
||||||
|
echo " ---"
|
||||||
|
echo " message: ${message}"
|
||||||
|
echo " severity: comment"
|
||||||
|
echo " ..."
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output a TAP compatible skipped test message
|
||||||
|
###
|
||||||
|
function _zunit_tap_skip() {
|
||||||
|
local message="$@"
|
||||||
|
|
||||||
|
echo "ok ${total} - # SKIP ${name}"
|
||||||
|
echo " ---"
|
||||||
|
echo " message: ${message}"
|
||||||
|
echo " severity: comment"
|
||||||
|
echo " ..."
|
||||||
|
}
|
||||||
115
src/zunit.zsh
Executable file
115
src/zunit.zsh
Executable file
|
|
@ -0,0 +1,115 @@
|
||||||
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Main zunit process #
|
||||||
|
######################
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output usage information and exit
|
||||||
|
###
|
||||||
|
function _zunit_usage() {
|
||||||
|
echo "$(color yellow 'Usage:')"
|
||||||
|
echo " zunit [options] [command] [tests...]"
|
||||||
|
echo
|
||||||
|
echo "$(color yellow 'Commands:')"
|
||||||
|
echo " init Bootstrap zunit in a new project"
|
||||||
|
echo " run [tests...] Run tests"
|
||||||
|
echo
|
||||||
|
echo "$(color yellow 'Options:')"
|
||||||
|
echo " -h, --help Output help text and exit"
|
||||||
|
echo " -v, --version Output version information and exit"
|
||||||
|
echo " -f, --fail-fast Stop the test runner immediately after the first failure"
|
||||||
|
echo " -t, --tap Output results in a TAP compatible format"
|
||||||
|
echo " --output-text Print results to a text log, in TAP compatible format"
|
||||||
|
echo " --output-html Print results to a HTML page"
|
||||||
|
echo " --allow-risky Supress warnings generated for risky tests"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# Output the version number
|
||||||
|
###
|
||||||
|
function _zunit_version() {
|
||||||
|
echo '0.6.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# The main zunit process
|
||||||
|
###
|
||||||
|
function _zunit() {
|
||||||
|
local help version ctx="$1" missing_dependencies=0 missing_config=1
|
||||||
|
|
||||||
|
if [[ -f .zunit.yml ]]; then
|
||||||
|
# Try and parse the config file within a subprocess,
|
||||||
|
# to avoid killing the main thread
|
||||||
|
$(eval $(_zunit_parse_yaml .zunit.yml 'zunit_config_') >/dev/null 2>&1)
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
# Perform the parse again, in context, so that the
|
||||||
|
# config vars are loaded into the enviroment
|
||||||
|
eval $(_zunit_parse_yaml .zunit.yml 'zunit_config_') >/dev/null 2>&1
|
||||||
|
missing_config=0
|
||||||
|
else
|
||||||
|
echo "\033[0;31mFailed to parse config file\033[0;m" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
$(type color >/dev/null 2>&1)
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
missing_dependencies=$(( missing_dependencies + 1 ))
|
||||||
|
echo "\033[0;31mMissing required dependency: Color - https://github.com/molovo/color\033[0;m" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
$(type revolver >/dev/null 2>&1)
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
missing_dependencies=$(( missing_dependencies + 1 ))
|
||||||
|
echo "\033[0;31mMissing required dependency: Revolver - https://github.com/molovo/revolver\033[0;m" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ $missing_dependencies -gt 0 ]] && exit 1
|
||||||
|
|
||||||
|
zparseopts -D -E \
|
||||||
|
h=help -help=help \
|
||||||
|
v=version -version=version
|
||||||
|
|
||||||
|
# If the version option is passed,
|
||||||
|
# output version information and exit
|
||||||
|
if [[ -n $version ]]; then
|
||||||
|
_zunit_version
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$ctx" in
|
||||||
|
init )
|
||||||
|
# If the help option is passed,
|
||||||
|
# output usage information and exit
|
||||||
|
if [[ -n $help ]]; then
|
||||||
|
_zunit_init_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_zunit_init "${(@)@:2}"
|
||||||
|
;;
|
||||||
|
run )
|
||||||
|
# If the help option is passed,
|
||||||
|
# output usage information and exit
|
||||||
|
if [[ -n $help ]]; then
|
||||||
|
_zunit_run_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_zunit_run "${(@)@:2}"
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
# If the help option is passed,
|
||||||
|
# output usage information and exit
|
||||||
|
if [[ -n $help ]]; then
|
||||||
|
_zunit_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_zunit_run "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_zunit "$@"
|
||||||
Loading…
Reference in a new issue