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