# # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # # This is free software; you can redistribute it and/or modify it # under the terms of the "Artistic License" which is located in the # file named "LICENSE.Artistic" which is included with this software. # # # This file was originally part of the "contrib" portion of the # TET3 test harness from The OpenGroup, originally found here: # http://tetworks.opengroup.org/tet/ or ftp://ftp.xopen.org/pub/TET3/ # and later published in hg.opensolaris.org/hg/test-dev/stcnv-gate # # A build of that repository delivers this file as part of the # SUNWstc-tetlite package, which carries the "Artistic" license # referred to above. # # # The "Artistic License" places some restrictions on the kinds of # changes that may be made to this file. The changes made here # relative to the original are "portability fixes" to allow these # functions to operate in the "test-runner" environment. # # The original from which this was derived can be found here: # https://bitbucket.org/illumos/illumos-stc/ # under the path: # usr/src/tools/tet/contrib/ctitools/src/lib/ksh/ctiutils.ksh ######################################################################## # # NAME: ctiutils.shlib # # SYNOPSIS: # cti_assert assertion_number assertion_msg # cti_report arg ... # cti_pass # cti_fail [msg ...] # cti_unresolved [msg ...] # cti_untested [msg ...] # cti_unsupported [msg ...] # cti_notinuse [msg ...] # cti_pathtrace # cti_checkpath expected-path-count # cti_deleteall reason # cti_reportfile path [title] # cti_rmf [files...] # cti_writefile path mode lines... # cti_appendfile path mode lines... # cti_execute [-c out|err] [-i input] result cmd [args...] # cti_runexpect failtext command pattern action [pattern action ...] # cti_expecttest failtext command pattern action [pattern action ...] # cti_cancel test_num [msg ...] [test result] # cti_cancelall [msg ...] [test result] # # DESCRIPTION: # Common korn shell functions for tests. # # cti_report() writes an informational line to the journal. # # The functions cti_pass(), cti_fail(), cti_unresolved(), # cti_untested(), cti_unsupported() and cti_notinuse() each # registers the corresponding result code against the current test, # and write any arguments to the execution results file, as a # single line. # # The cti_pathtrace() and cti_checkpath() are used in path tracing. # cti_pathtrace() increments the variable pathok. cti_checkpath() # checks the path tracing and registers a PASS result if # appropriate. # # cti_deleteall() cancels all tests in the current test case. # # cti_reportfile() writes the contents of a file to the journal. # # cti_rmf() removes files. # # cti_writefile() writes to the contents of a file; # cti_appendfile() appends to contents of a file. # # cti_execute() executes a command. # # cti_runexpect() runs the expect utility. cti_expecttest() is # like cti_runexpect() except that it is written with path tracing # and is designed to do the complete work of a test purpose. # cti_runexpect() runs the expect utility. cti_expecttest() is # like cti_runexpect() except that it is written with path tracing # and is designed to do the complete work of a test purpose. # # cti_cancel() cancels the dedicated test purpose in the current test # case from execution with the test result that user gives. It will work # in startup function. # # cti_cancelall() cancels all tests in the current test case. It could # be used in startup function to cancel the execution of test cases # with the test result that user gives. # ######################################################################## # # cti_lf_checkargs() - check number of arguments passed to a shell function. # # Usage: cti_lf_checkargs argc expected-argc operator funcname # # operator can be EQ or GE. # # Returns 0 if the expected number of arguments were passed, non-zero # otherwise. # function cti_lf_checkargs { typeset -i cti_lv_argc=$1 typeset -i cti_lv_expargc=$2 typeset cti_lv_op="$3" typeset cti_lv_funcname="$4" case "$cti_lv_op" in EQ) if test $cti_lv_argc -ne $cti_lv_expargc then cti_unresolved "Test coding error:" \ "$cti_lv_funcname() called with $cti_lv_argc" \ "args, need $cti_lv_expargc" return 1 fi ;; GE) if test $cti_lv_argc -lt $cti_lv_expargc then cti_unresolved "Test coding error:" \ "$cti_lv_funcname() called with $cti_lv_argc" \ "args, need >= $cti_lv_expargc" return 1 fi ;; *) cti_unresolved "Internal error: cti_lf_checkargs()" \ "called for $funcname() with operator $cti_lv_op" return 1 ;; esac return 0 } # # cti_result() - register a result and write informational line to journal. # # Usage: cti_result result [msg ...] # # If the current test function is not a startup or cleanup, this routine # registers the specified result code for the current test. The remaining # arguments, if any, are written to the execution results file as a single # line. # # Modifications for test-runner: # Print the result and test name (in place of tet_result) # On failure, return non-zero to tell test-runner our status. # function cti_result { typeset res typeset -i rv=0 test $# -eq 0 && return res=$1 shift my_host=`hostname` my_timestamp=`date | awk '{print $4}'` test $# -gt 0 && print "$my_host $my_timestamp $@" # Print the result and test name (as tet_result would) print "$res: ${tc_id:-$(basename $0)}" # Return non-zero for failures. See codes in: # test-runner/stf/include/stf.shlib case "$res" in PASS) ;; FAIL) rv=1 ;; UNRESOLVED) rv=2 ;; NOTINUSE) rv=3 ;; UNSUPPORTED) rv=4 ;; UNTESTED) rv=5 ;; UNINITIATED) rv=6 ;; NORESULT) rv=7 ;; WARNING) rv=8 ;; TIMED_OUT) rv=9 ;; *) echo "cti_result: $res: unknown result code" rv=10 ;; esac return $rv } # # cti_report() - write an informational line to the journal # # Usage: cti_report arg ... # # Writes the arguments to the execution results file, as a single line. # function cti_report { my_host=`hostname` my_timestamp=`date | awk '{print $4}'` print "$my_host $my_timestamp $@" } # # cti_assert() - write an Assert line to the journal # # Usage: cti_assert assertion_number assertion_msg # # Writes the arguments to the execution results file, as a single line. # function cti_assert { cti_lf_checkargs $# 2 GE cti_assert || return 1 cti_report "ASSERT $1: $2" } # # cti_pass() - register a PASS result. # # Usage: cti_pass [msg ...] # function cti_pass { cti_result PASS "$@" } # # cti_fail() - register a FAIL result. # # Usage: cti_fail [msg ...] # # Registers a FAIL result for the current test, and writes any arguments to # the execution results file, as a single line. # function cti_fail { cti_result FAIL "$@" } # # cti_unresolved() - register an UNRESOLVED result. # # Usage: cti_unresolved [msg ...] # # Registers an UNRESOLVED result for the current test, and writes any arguments # to the execution results file, as a single line. # function cti_unresolved { cti_result UNRESOLVED "$@" } # # cti_uninitiated() - register an UNINITIATED result. # # Usage: cti_uninitiated [msg ...] # # Registers an UNINITIATED result for the current test, and writes any arguments # to the execution results file, as a single line. # function cti_uninitiated { cti_result UNINITIATED "$@" } # # cti_untested() - register an UNTESTED result. # # Usage: cti_untested [msg ...] # # Registers an UNTESTED result for the current test, and writes any arguments # to the execution results file, as a single line. # function cti_untested { cti_result UNTESTED "$@" } # # cti_unsupported() - register an UNSUPPORTED result. # # Usage: cti_unsupported [msg ...] # # Registers an UNSUPPORTED result for the current test, and writes any # arguments to the execution results file, as a single line. # function cti_unsupported { cti_result UNSUPPORTED "$@" } # # cti_notinuse() - register a NOTINUSE result. # # Usage: cti_notinuse [msg ...] # # Registers a NOTINUSE result for the current test, and writes any arguments # to the execution results file, as a single line. # function cti_notinuse { cti_result NOTINUSE "$@" } # # cti_pathtrace() - increment path counter. # # Usage: cti_pathtrace # # Increments variable pathok. Like C macro PATH_TRACE. # function cti_pathtrace { : $((pathok += 1)) } # # cti_checkpath() - check path tracing and register a PASS result. # # Usage: cti_checkpath expected-path-count # # Like C macro PATH_XS_RPT(). # function cti_checkpath { cti_lf_checkargs $# 1 EQ cti_checkpath || return if test $pathok -eq $1 then cti_pass else cti_unresolved "Path tracing error: path counter $pathok," \ "expecting $1" fi } # # cti_deleteall() - cancel all tests. # # Usage: cti_deleteall reason # # Cancels all tests # function cti_deleteall { typeset cti_lv_ic typeset cti_lv_tp test $# -eq 0 && return for cti_lv_ic in $iclist do for cti_lv_tp in `eval echo \\$$cti_lv_ic` do if test -n "$cti_lv_tp" then echo "Deleted test: $cti_lv_tp" "$@" fi done done } # # cti_reportfile() - write the contents of a file to the journal. # # Usage: cti_reportfile path [title] # # Writes the contents of the file specified by path to the execution results # file, line by line. # function cti_reportfile { typeset cti_lv_path=$1 typeset cti_lv_title="${2:-$cti_lv_path}" typeset cti_lv_line cti_lf_checkargs $# 1 GE cti_reportfile || return cti_report "+++ $cti_lv_title +++" /usr/bin/cat $cti_lv_path cti_report "+++ end +++" cti_report " " } # # cti_rmf() - remove files. # # Usage: cti_rmf [files...] # # Calls "rm -f" to remove the files, and verifies that they have been removed. # # Returns 0 on success, non-zero if any of the files could not be removed. # function cti_rmf { typeset cti_lv_file for cti_lv_file in "$@" do rm -f "$cti_lv_file" if test -f "$cti_lv_file" then cti_unresolved "Error removing file \"$cti_lv_file\"" return 1 fi done return 0 } # # cti_writefile() - write contents of a file. # # Usage: cti_writefile path mode lines... # # Truncates the file specified by path and then writes the third and # subsequent arguments to the specified file as separate lines. # # Returns 0 on success, non-zero if any of the files could not be removed. # function cti_writefile { cti_lf_checkargs $# 3 GE cti_writefile || return 1 cti_rmf "$1" || return 1 cti_appendfile "$@" } # # cti_appendfile() - append to contents of a file. # # Usage: cti_appendfile path mode lines... # # Appends the third and subsequent arguments to the specified file as separate # lines. # # Returns 0 on success, non-zero if any of the files could not be removed. # function cti_appendfile { typeset cti_lv_path="$1" typeset cti_lv_mode="$2" typeset cti_lv_line cti_lf_checkargs $# 3 GE cti_appendfile || return 1 shift 2 for cti_lv_line in "$@" do echo "$cti_lv_line" >> "$cti_lv_path" if [[ $? -ne 0 ]] then cti_unresolved \ "Error writing to file \"$cti_lv_path\"" return 1 fi done cti_execute UNRESOLVED chmod "$cti_lv_mode" "$cti_lv_path" return $? } # # cti_execute() - execute a command # # Usage: cti_execute [-c out|err] [-i input] result cmd [args...] # # Executes a command. The standard output is redirected to the file cti_stdout # and the standard error is redirected to the file cti_stderr. # # If the command has a non-zero exit status, cti_execute() registers a result # code of `result'. # # Options: # -c out|err Check standard output/error. If anything is written to # the specified output channel, a result code of `result' # is registered and the output written to the journal. # May have multiple -c options. # -i input Use -i as an input line to the command. # May have multiple -i options. # # Returns 0 on success, non-zero on failure (returning the # exit status from the command when possible). # function cti_execute { typeset cti_lv_opt typeset -i cti_lv_checkstdout=0 typeset -i cti_lv_checkstderr=0 typeset cti_lv_result typeset -i cti_lv_status typeset -i cti_lv_rv=0 # Remove files used for redirecting standard I/O. cti_rmf cti_stdin cti_stdout cti_stderr || return 1 # Create (empty) files to take standard output and error so there are # no problems later when we come to run the command. touch cti_stdout cti_stderr if [[ $? -ne 0 ]] then cti_unresolved "Error creating files cti_stdout and cti_stderr" return 1 fi # Parse command line options while getopts "c:i:l:s:" cti_lv_opt do case $cti_lv_opt in c) case "$OPTARG" in out|err) eval cti_lv_checkstd$OPTARG=1 ;; *) cti_unresolved "cti_execute() called with" \ "bad option argument -c $OPTARG" return 1 ;; esac ;; i) echo "$OPTARG" >> cti_stdin if [[ $? -ne 0 ]] then cti_unresolved "Error writing to cti_stdin" return 1 fi ;; *) cti_unresolved "cti_execute() called with illegal" \ "option $cti_lv_opt" return 1 ;; esac done shift $((OPTIND-1)) # Verify the correct number of arguments were passed. cti_lf_checkargs $# 2 GE cti_execute || return 1 # First (non-option) argument is the result code to use if the command # fails. cti_lv_result="${1:-UNRESOLVED}" shift # Execute the command, redirecting standard input if required. if test -f cti_stdin then eval "$@" < cti_stdin > cti_stdout 2> cti_stderr else eval "$@" > cti_stdout 2> cti_stderr fi cti_lv_status=$? # Check the exit status of the command if test $cti_lv_status -ne 0 then if [[ "$cti_lv_result" = "CTI" ]] then cti_report CTI "Command \"$*\" failed "\ "with status $cti_lv_status" else cti_result "$cti_lv_result"\ "Command \"$*\" failed "\ "with status $cti_lv_status" cti_lv_rv=$cti_lv_status fi fi # Always log output of cti_execute_cmd if [[ "$cti_lv_result" = "CTI" ]] then if test -s cti_stdout then cti_reportfile cti_stdout "Standard output from command \"$*\"" fi if test -s cti_stderr then cti_reportfile cti_stderr "Standard error from command \"$*\"" fi return $cti_lv_status fi # If cmd failed, or if "-c err", check standard error. if test \( $cti_lv_rv -ne 0 -o $cti_lv_checkstderr -eq 1 \) \ -a -s cti_stderr then cti_result "$cti_lv_result" \ "Command \"$*\" produced standard error" cti_reportfile cti_stderr "Standard error from command \"$*\"" [[ $cti_lv_rv = 0 ]] && cti_lv_rv=1 fi # If cmd failed, or if "-c out", check standard output. if test \( $cti_lv_rv -ne 0 -o $cti_lv_checkstdout -eq 1 \) \ -a -s cti_stdout then cti_result "$cti_lv_result" \ "Command \"$*\" produced standard output" cti_reportfile cti_stdout "Standard output from command \"$*\"" [[ $cti_lv_rv = 0 ]] && cti_lv_rv=1 fi return $cti_lv_rv } # # Exit values for expect scripts. # N.B. Do not use 0, as expect uses this for e.g. syntax errors. # typeset -ri CTI_EXP_RV_TIMEDOUT=1 typeset -ri CTI_EXP_RV_TESTFAIL=2 typeset -ri CTI_EXP_RV_OK=3 # # cti_runexpect() - run the expect utility. # # Usage: cti_runexpect failtext command pattern action [pattern action ...] # # Executes the expect utility using the command line specified in the second # argument. Generates a temporary expect script which is removed at the end of # the call. The arguments following the second are pattern-action pairs. The # pattern can be a regular expression or "CALL", which indicates the action is # simply a function call which is unconditionally executed at that point. # # The following expect functions are available: # # startcritical Indicates that failures from this point onwards # constitute a test failure. In that case the # ``failtext'' argument is used to report the error # message. # endcritical Indicates the end of the test failure section begun by # startcritical. # finish Exit the expect script here - the test has passed. # # Returns 0 on success, non-zero on failure. # function cti_runexpect { typeset -ri CTI_EXP_TIMEOUT=5 typeset -r cti_lv_expfile="./cti_$tc_id-$$.exp" typeset cti_lv_failtext="$1" typeset cti_lv_command="$2" typeset -i cti_lv_rv=0 typeset cti_lv_dopt # Verify the correct number of arguments were passed. cti_lf_checkargs $# 4 GE cti_runexpect || return 1 shift 2 # Generate expect script. { echo "set STATUS_OK $CTI_EXP_RV_OK" echo "set STATUS_FAIL $CTI_EXP_RV_TESTFAIL" echo "set STATUS_TIMEDOUT $CTI_EXP_RV_TIMEDOUT" echo '' echo "set timeout $CTI_EXP_TIMEOUT" echo 'set retval $STATUS_TIMEDOUT' echo '' echo 'eval spawn [lrange $argv 0 end]' echo '' echo 'proc startcritical {} {' echo ' global retval' echo ' global STATUS_FAIL' echo ' set retval $STATUS_FAIL' echo '}' echo '' echo 'proc endcritical {} {' echo ' global retval' echo ' global STATUS_TIMEDOUT' echo ' set retval $STATUS_TIMEDOUT' echo '}' echo '' echo 'proc finish {} {' echo ' global STATUS_OK' echo ' exit $STATUS_OK' echo '}' while test $# -gt 1 do echo '' if test "$1" = "CALL" then echo "$2" else echo 'expect {' echo " -re \"$1\" {" echo " $2 " echo ' }' echo ' timeout {' echo ' exit $retval' echo ' }' echo '}' fi shift 2 done } > $cti_lv_expfile # Check that there were no errors writing to the script file. if test $? -ne 0 then cti_unresolved "Error writing expect script to file" \ "\"$cti_lv_expfile\"" return 1 fi # If debug is on, turn on expect debug flag. case "$CTI_SHELL_DEBUG" in y*|Y*) cti_lv_dopt='-d' ;; esac # Execute expect using generated script. expect $cti_lv_dopt -f $cti_lv_expfile $cti_lv_command > cti_expout 2>&1 cti_lv_rv=$? # If debug is on, save expect script and output, otherwise remove # script. case "$CTI_SHELL_DEBUG" in y*|Y*) cp cti_expout ${cti_lv_expfile%.exp}.out cti_report "DEBUG: expect script is $PWD/$cti_lv_expfile," \ "expect output is in $PWD/${cti_lv_expfile%.exp}.out" ;; *) rm -f $cti_lv_expfile esac # Deal with return value from expect. case $cti_lv_rv in $CTI_EXP_RV_OK) return 0 ;; $CTI_EXP_RV_TIMEDOUT) cti_unresolved "Expect script timed-out during test setup" ;; $CTI_EXP_RV_TESTFAIL) cti_fail "$cti_lv_failtext" ;; *) cti_unresolved "Test coding error or expect bug:" \ "unexpected exit status $cti_lv_rv from expect script" cti_reportfile cti_expout "Output from expect" ;; esac return 1 } # # cti_expecttest() - run the expect utility. # # Usage: cti_expecttest failtext command pattern action [pattern action ...] # # Common test function for test purposes which use expect. Like cti_runexpect() # except that this is written with path tracing and is designed to do the # complete work of a test purpose. # function cti_expecttest { # Verify the correct number of arguments were passed. cti_lf_checkargs $# 4 GE cti_expecttest || return # Use cti_runexpect() to execute expect utililty. if cti_runexpect "$@" then cti_pathtrace else return fi cti_checkpath 1 } function cti_execute_cmd { cti_execute CTI "$@" } # # "private" functions for internal use by cti_cancel function # used to replace test purpose functions which will be canceled. # function cancel_ic { cti_result $cticancel_result "$cticancel_msg" } # # cti_cancel() - cancel an individual test purpose. # # Usage: cti_cancel tp reason [test result] # # Cancels an individual test by replace the tp function with # cancel_ic function # function cti_cancel { test $# -gt 3 && return test_num=$1 cticancel_msg="$2" cticancel_result=${3:-"UNSUPPORTED"} typeset cti_lv_ic cti_lv_ic_mod typeset cti_lv_tp cti_report "Canceling test $test_num: $cticancel_msg" # # translate the ic and point the test purpose ic to # cancel_ic function # for cti_lv_ic in $iclist do cti_lv_ic_mod="" for cti_lv_tp in `eval echo \\$$cti_lv_ic` do if [ X"$cti_lv_tp" == X"$test_num" ]; then cti_lv_ic_mod=$cti_lv_ic_mod" cancel_ic" else cti_lv_ic_mod=$cti_lv_ic_mod" $cti_lv_tp" fi done eval "$cti_lv_ic=\"$cti_lv_ic_mod\"" done } # # cti_cancelall() - cancel all tests. # # Usage: cti_cancelall reason [test result] # # Cancels all tests by replace the tests functions with cancel_ic function # function cti_cancelall { typeset cti_lv_ic typeset cti_lv_tp cticancel_msg=$1 cticancel_result=${2:-"UNSUPPORTED"} test $# -eq 0 && return for cti_lv_ic in $iclist do for cti_lv_tp in `eval echo \\$$cti_lv_ic` do cti_cancel $cti_lv_tp "$cticancel_msg" \ $cticancel_result done done }