Blame SOURCES/catest

906690
#!/bin/bash
906690
#
906690
# This Source Code Form is subject to the terms of the Mozilla Public
906690
# License, v. 2.0. If a copy of the MPL was not distributed with this
906690
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
906690
#
906690
906690
#
906690
# Copyright (c) 2014, Joyent, Inc.
906690
#
906690
906690
#
906690
# catest: a simple testing tool and framework. See usage below for details.
906690
#
906690
906690
shopt -s xpg_echo
906690
906690
#
906690
# Global configuration
906690
#
906690
cat_arg0=$(basename $0)			# canonical name of "catest"
906690
cat_outbase="catest.$$"			# output directory name
906690
cat_tstdir="test"			# test directory
906690
906690
#
906690
# Options and arguments
906690
#
906690
cat_tests=""				# list of tests (absolute paths)
906690
opt_a=false				# run all tests
906690
opt_c=false				# colorize test results
906690
opt_k=false				# keep output of successful tests
906690
opt_o="/var/tmp"			# parent directory for output directory
906690
opt_t=					# TAP format output file
906690
opt_S=false				# Non-strict mode for js tests
906690
906690
#
906690
# Current state
906690
#
906690
cat_outdir=				# absolute path to output directory
906690
cat_tapfile=				# absolute path of TAP output file
906690
cat_ntests=				# total number of tests
906690
cat_nfailed=0				# number of failed tests run
906690
cat_npassed=0				# number of successful tests run
906690
cat_nrun=0				# total number of tests run
906690
906690
#
906690
# Allow environment-specific customizations.
906690
#
906690
[[ -f $(dirname $0)/catest_init.sh ]] && . $(dirname $0)/catest_init.sh
906690
906690
#
906690
# fail MSG: emits the given error message to stderr and exits non-zero.
906690
#
906690
function fail
906690
{
906690
	echo "$cat_arg0: $@" >&2
906690
906690
	[[ -n $cat_tapfile ]] && echo "Bail out! $@" >> $cat_tapfile
906690
906690
	exit 1
906690
}
906690
906690
#
906690
# usage [MSG]: emits the given message, if any, and a usage message, then exits.
906690
#
906690
function usage
906690
{
906690
	[[ $# -ne 0 ]] && echo "$cat_arg0: $@\n" >&2
906690
906690
	cat <<USAGE >&2
906690
Usage: $cat_arg0 [-k] [-c] [-o dir] [-t file] test1 ...
906690
       $cat_arg0 [-k] [-c] [-o dir] [-t file] -a
906690
906690
In the first form, runs specified tests.  In the second form, runs all tests
906690
found under "$cat_tstdir" of the form "tst*.<ext>" for supported extensions.
906690
906690
TESTS
906690
906690
    Tests are just files to be executed by some interpreter. In most cases, a
906690
    test succeeds if it exits successfully and fails otherwise.  You can also
906690
    specify the expected stdout of the test in a file with the same name as the
906690
    test plus a ".out" suffix, in which case the test will also fail if the
906690
    actual output does not match the expected output.
906690
906690
    Supported interpreter extensions are "sh" (bash) and "js" (node).
906690
906690
    This framework does not provide per-test setup/teardown facilities, but
906690
    test files can do whatever they want, including making use of common
906690
    libraries for setup and teardown.
906690
906690
TEST OUTPUT
906690
906690
    Summary output is printed to stdout.  TAP output can be emitted with "-t".
906690
906690
    Per-test output is placed in a new temporary directory inside the directory
906690
    specified by the -o option, or /var/tmp if -o is not specified.
906690
906690
    Within the output directory will be a directory for each failed test which
906690
    includes a README describing why the test failed (e.g., exited non-zero), a
906690
    copy of the test file itself, the actual stdout and stderr of the test, and
906690
    the expected stdout of the test (if specified).
906690
906690
    If -k is specified, the output directory will also include a directory for
906690
    each test that passed including the stdout and stderr from the test.
906690
906690
The following options may be specified:
906690
906690
	-a 		Runs all tests under $cat_tstdir
906690
			(ignores other non-option arguments)
906690
	-c		Color code test result messages
906690
	-h		Output this message
906690
	-k		Keep output from all tests, not just failures
906690
	-o directory	Specifies the output directory for tests
906690
			(default: /var/tmp)
906690
	-S		Turn off strict mode for tests
906690
	-t file		Emit summary output in TAP format
906690
906690
USAGE
906690
906690
	exit 2
906690
}
906690
906690
#
906690
# abspath FILE: emits a canonical, absolute path to the given file or directory.
906690
#
906690
function abspath
906690
{
906690
	local dir=$(dirname $1) base=$(basename $1)
906690
906690
	if [[ $base = ".." ]]; then
906690
		cd "$dir"/.. > /dev/null || fail "abspath '$1': failed to chdir"
906690
		pwd
906690
		cd - > /dev/null || fail "abspath '$1': failed to chdir back"
906690
	else
906690
		cd "$dir" || fail "abspath '$1': failed to chdir"
906690
		echo "$(pwd)/$base"
906690
		cd - > /dev/null || fail "abspath '$1': failed to chdir back"
906690
	fi
906690
}
906690
906690
#
906690
# cleanup_test TESTDIR "success" | "failure": cleans up the output directory
906690
#     for this test
906690
#
906690
function cleanup_test
906690
{
906690
	local test_odir="$1" result=$2
906690
	local newdir
906690
906690
	if [[ $result = "success" ]]; then
906690
		newdir="$(dirname $test_odir)/success.$cat_npassed"
906690
	else
906690
		newdir="$(dirname $test_odir)/failure.$cat_nfailed"
906690
	fi
906690
906690
	mv "$test_odir" "$newdir"
906690
	echo $newdir
906690
}
906690
906690
#
906690
# emit_failure TEST ODIR REASON: indicate that a test has failed
906690
#
906690
function emit_failure
906690
{
906690
	local test_label=$1 odir=$2 reason=$3
906690
906690
	if [[ $cat_tapfile ]]; then
906690
		echo "not ok $(($cat_nrun+1)) $test_label" >> $cat_tapfile
906690
	fi
906690
906690
	echo "${TRED}FAILED.${TCLEAR}"
906690
	echo "$test_path failed: $reason" > "$odir/README"
906690
906690
	[[ -n "$odir" ]] && echo ">>> failure details in $odir\n"
906690
	((cat_nfailed++))
906690
}
906690
906690
#
906690
# emit_pass TEST: indicate that a test has passed
906690
#
906690
function emit_pass
906690
{
906690
	local test_label=$1
906690
906690
	if [[ $cat_tapfile ]]; then
906690
		echo "ok $((cat_nrun+1)) $test_label" >> $cat_tapfile
906690
	fi
906690
906690
	echo "${TGREEN}success.${TCLEAR}"
906690
	((cat_npassed++))
906690
}
906690
906690
#
906690
# Executes a single test
906690
#
906690
# Per-test actions:
906690
# - Make a directory for that test
906690
# - cd into that directory and exec the test
906690
# - Redirect standard output and standard error to files
906690
# - Tests return 0 to indicate success, non-zero to indicate failure
906690
#
906690
function execute_test
906690
{
906690
	[[ $# -eq 1 ]] || fail "Missing test to execute"
906690
	local test_path=$1
906690
	local test_name=$(basename $1)
906690
	local test_dir=$(dirname $1)
906690
	local test_label=$(echo $test_path | sed -e s#^$SRC/##)
906690
	local test_odir="$cat_outdir/test.$cat_nrun"
906690
	local ext=${test_name##*.}
906690
	local faildir
906690
	local EXEC
906690
906690
	echo "Executing test $test_label ... \c "
906690
	mkdir "$test_odir" >/dev/null || fail "failed to create test directory"
906690
	cp "$test_path" "$test_odir"
906690
906690
	case "$ext" in
906690
	"sh") 	EXEC=bash ;;
906690
	"js") 	EXEC=node ;;
906690
	*) 	faildir=$(cleanup_test "$test_odir" "failure")
906690
		emit_failure "$test_label" "$faildir" "unknown file extension"
906690
		return 0
906690
		;;
906690
	esac
906690
906690
	pushd "$test_dir" >/dev/null
906690
	if [[ $opt_S ]]; then
906690
		$EXEC $test_name -S >$test_odir/$$.out 2>$test_odir/$$.err
906690
	else
906690
		$EXEC $test_name >$test_odir/$$.out 2>$test_odir/$$.err
906690
	fi
906690
	execres=$?
906690
	popd > /dev/null
906690
906690
	if [[ $execres != 0 ]]; then
906690
		faildir=$(cleanup_test "$test_odir" "failure")
906690
		emit_failure "$test_label" "$faildir" "test returned $execres"
906690
		return 0
906690
	fi
906690
906690
	if [[ -f $test_path.out ]] && \
906690
	    ! diff $test_path.out $test_odir/$$.out > /dev/null ; then
906690
		cp $test_path.out $test_odir/$test_name.out
906690
		faildir=$(cleanup_test "$test_odir" "failure")
906690
		emit_failure "$test_label" "$faildir" "stdout mismatch"
906690
		return 0
906690
	fi
906690
906690
	cleanup_test "$test_odir" "success" > /dev/null
906690
	emit_pass "$test_label"
906690
}
906690
906690
while getopts ":o:t:ackSh?" c $@; do
906690
	case "$c" in
906690
	a|c|k|S) 	eval opt_$c=true ;;
906690
	o|t) 	eval opt_$c="$OPTARG" ;;
906690
	h)	usage ;;
906690
	:) 	usage "option requires an argument -- $OPTARG" ;;
906690
	*) 	usage "invalid option: $OPTARG" ;;
906690
	esac
906690
done
906690
906690
#
906690
# If configured to use terminal colors, record the escape sequences here.
906690
#
906690
if [[ $opt_c == "true" && -t 1 ]]; then
906690
    TGREEN="$(tput setaf 2)"
906690
    TRED="$(tput setaf 1)"
906690
    TCLEAR="$(tput sgr0)"
906690
fi
906690
906690
shift $((OPTIND-1))
906690
[[ $# -eq 0 && $opt_a == "false" ]] && \
906690
    usage "must specify \"-a\" or list of tests"
906690
906690
#
906690
# Initialize paths and other environment variables.
906690
#
906690
export SRC=$(abspath $(dirname $0)/../..)
906690
export PATH=$SRC/deps/ctf2json:$PATH
906690
[[ -n $HOST ]] || export HOST=$(hostname)
906690
906690
#
906690
# We create and set CATMPDIR as a place for the tests to store temporary files.
906690
#
906690
export CATMPDIR="/var/tmp/catest.$$_tmpfiles"
906690
906690
if [[ $opt_a = "true" ]]; then
906690
	cat_tests=$(find $SRC/$cat_tstdir \
906690
	    -name 'tst*.js' -o -name 'tst*.sh') || \
906690
	    fail "failed to locate tests in $SRC/$cat_tstdir"
906690
	cat_tests=$(sort <<< "$cat_tests")
906690
else
906690
	for t in $@; do
906690
		[[ -f $t ]] || fail "cannot find test $t"
906690
		cat_tests="$cat_tests $(abspath $t)"
906690
	done
906690
fi
906690
906690
mkdir -p "$opt_o/$cat_outbase"
906690
cat_outdir=$(abspath $opt_o/$cat_outbase)
906690
906690
mkdir -p $CATMPDIR || fail "failed to create $CATMPDIR"
906690
906690
cat_ntests=$(echo $cat_tests | wc -w)
906690
printf "Configuration:\n"
906690
printf "    SRC:                         $SRC\n"
906690
printf "    Output directory:            $cat_outdir\n"
906690
printf "    Temp directory:              $CATMPDIR\n"
906690
if [[ -n "$opt_t" ]]; then
906690
    cat_tapfile=$(abspath $opt_t)
906690
    printf "    TAP output:                  $cat_tapfile\n"
906690
fi
906690
printf "    Keep successful test output: $opt_k\n"
906690
printf "    Found %d test(s) to run\n\n" $cat_ntests
906690
906690
#
906690
# Validate parameters and finish setup.
906690
#
906690
[[ $cat_ntests -gt 0 ]] || fail "no tests found"
906690
906690
if [[ -n "$cat_tapfile" ]]; then
906690
	echo "1..$(($cat_ntests))" > $cat_tapfile || \
906690
	    fail "failed to emit TAP output"
906690
fi
906690
906690
#
906690
# Allow for environment-specific customizations.  These are optionally loaded
906690
# by the catest_init.sh file sourced earlier.
906690
#
906690
if type catest_init > /dev/null 2>&1 && ! catest_init; then
906690
	fail "catest_init failed"
906690
fi
906690
906690
#
906690
# Start the test run.
906690
#
906690
printf "===================================================\n\n"
906690
906690
for t in $cat_tests; do
906690
	execute_test $t
906690
	((cat_nrun++))
906690
done
906690
906690
printf "\n===================================================\n\n"
906690
printf "Results:\n"
906690
printf "\tTests passed:\t%2d/%2d\n" $cat_npassed $cat_nrun
906690
printf "\tTests failed:\t%2d/%2d\n" $cat_nfailed $cat_nrun
906690
printf "\n===================================================\n"
906690
906690
if [[ $opt_k == "false" ]]; then
906690
	echo "Cleaning up output from successful tests ... \c "
906690
	rm -rf $cat_outdir/success.*
906690
	rm -rf $CATMPDIR
906690
	echo "done."
906690
fi
906690
906690
exit $cat_nfailed