| #!/bin/sh | |
| #set -x | |
| cd $(dirname "$0") | |
| default_path=".." | |
| default_cmd="sparse \$file" | |
| default_args="$SPARSE_TEST_ARGS" | |
| tests_list="" | |
| prog_name=`basename $0` | |
| if [ ! -x "$default_path/sparse-llvm" ]; then | |
| disabled_cmds="sparsec sparsei sparse-llvm sparse-llvm-dis" | |
| fi | |
| if [ ! -x "$default_path/scheck" ]; then | |
| disabled_cmds="$disabled_cmds scheck" | |
| fi | |
| # flags: | |
| # - some tests gave an unexpected result | |
| failed=0 | |
| # counts: | |
| # - tests that have not been converted to test-suite format | |
| # - tests that are disabled | |
| # - tests that passed | |
| # - tests that failed | |
| # - tests that failed but are known to fail | |
| unhandled_tests=0 | |
| disabled_tests=0 | |
| ok_tests=0 | |
| ko_tests=0 | |
| known_ko_tests=0 | |
| # defaults to not verbose | |
| [ -z "$V" ] && V=0 | |
| vquiet="" | |
| quiet=0 | |
| abort=0 | |
| ## | |
| # verbose(string) - prints string if we are in verbose mode | |
| verbose() | |
| { | |
| [ "$V" -eq "1" ] && echo " $1" | |
| return 0 | |
| } | |
| ## | |
| # warning(string) - prints a warning | |
| warning() | |
| { | |
| [ "$quiet" -ne 1 ] && echo "warning: $1" | |
| return 0 | |
| } | |
| ## | |
| # error(string[, die]) - prints an error and exits with value die if given | |
| error() | |
| { | |
| [ "$quiet" -ne 1 ] && echo "error: $1" | |
| [ -n "$2" ] && exit $2 | |
| return 0 | |
| } | |
| ## | |
| # get_tag_value(file) - get the 'check-<...>' tags & values | |
| get_tag_value() | |
| { | |
| check_name="" | |
| check_command="$default_cmd" | |
| check_exit_value=0 | |
| check_timeout=0 | |
| check_known_to_fail=0 | |
| check_error_ignore=0 | |
| check_output_ignore=0 | |
| check_output_contains=0 | |
| check_output_excludes=0 | |
| check_output_pattern=0 | |
| check_output_match=0 | |
| check_output_returns=0 | |
| check_arch_ignore="" | |
| check_arch_only="" | |
| check_assert="" | |
| check_cpp_if="" | |
| lines=$(grep '^ \* check-[a-z-]*' $1 | \ | |
| sed -e 's/^ \* \(check-[a-z-]*:*\) *\(.*\)$/\1 \2/') | |
| while read tag val; do | |
| #echo "-> tag: '$tag'" | |
| #echo "-> val: '$val'" | |
| case $tag in | |
| check-name:) check_name="$val" ;; | |
| check-command:) check_command="$val" ;; | |
| check-exit-value:) check_exit_value="$val" ;; | |
| check-timeout:) [ -z "$val" ] && val=1 | |
| check_timeout="$val" ;; | |
| check-known-to-fail) check_known_to_fail=1 ;; | |
| check-error-ignore) check_error_ignore=1 ;; | |
| check-output-ignore) check_output_ignore=1 ;; | |
| check-output-contains:) check_output_contains=1 ;; | |
| check-output-excludes:) check_output_excludes=1 ;; | |
| check-output-pattern) check_output_pattern=1 ;; | |
| check-output-match) check_output_match=1 ;; | |
| check-output-returns:) check_output_returns=1 ;; | |
| check-arch-ignore:) arch=$(uname -m) | |
| check_arch_ignore="$val" ;; | |
| check-arch-only:) arch=$(uname -m) | |
| check_arch_only="$val" ;; | |
| check-assert:) check_assert="$val" ;; | |
| check-cpp-if:) check_cpp_if="$val" ;; | |
| check-description:) ;; # ignore | |
| check-note:) ;; # ignore | |
| check-warning:) ;; # ignore | |
| check-error-start) ;; # ignore | |
| check-error-end) ;; # ignore | |
| check-output-start) ;; # ignore | |
| check-output-end) ;; # ignore | |
| check-should-pass) ;; # ignore, unused annotation | |
| check-should-fail) ;; # ignore, unused annotation | |
| check-should-warn) ;; # ignore, unused annotation | |
| check-*) error "$1: unknown tag '$tag'" 1 ;; | |
| esac | |
| done << EOT | |
| $lines | |
| EOT | |
| } | |
| ## | |
| # helper for has_(each|none)_patterns() | |
| has_patterns() | |
| { | |
| ifile="$1" | |
| patt="$2" | |
| ofile="$3" | |
| cmp="$4" | |
| msg="$5" | |
| grep "$patt:" "$ifile" | \ | |
| sed -e "s/^.*$patt: *\(.*\)$/\1/" | \ | |
| while read val; do | |
| grep -s -q "$val" "$ofile" | |
| if [ "$?" $cmp 0 ]; then | |
| error " Pattern '$val' unexpectedly $msg" | |
| return 1 | |
| fi | |
| done | |
| return $? | |
| } | |
| ## | |
| # has_each_patterns(ifile tag ofile) - does ofile contains some | |
| # of the patterns given by ifile's tags? | |
| # | |
| # returns 0 if all present, 1 otherwise | |
| has_each_patterns() | |
| { | |
| has_patterns "$1" "$2" "$4" -ne "$3" | |
| } | |
| ## | |
| # has_none_patterns(ifile tag ofile) - does ofile contains some | |
| # of the patterns given by ifile's tags? | |
| # | |
| # returns 1 if any present, 0 otherwise | |
| has_none_patterns() | |
| { | |
| has_patterns "$1" "$2" "$4" -eq "$3" | |
| } | |
| ## | |
| # minmax_patterns(ifile tag ofile) - does ofile contains the | |
| # the patterns given by ifile's tags | |
| # the right number of time? | |
| minmax_patterns() | |
| { | |
| ifile="$1" | |
| patt="$2" | |
| ofile="$3" | |
| grep "$patt([0-9-]*\(, *\)*[0-9-]*):" "$ifile" | \ | |
| sed -e "s/^.*$patt(\([0-9]*\)): *\(.*\)/\1 eq \2/" \ | |
| -e "s/^.*$patt(\([0-9-]*\), *\([0-9-]*\)): *\(.*\)/\1 \2 \3/" | \ | |
| while read min max pat; do | |
| n=$(grep -s "$pat" "$ofile" | wc -l) | |
| if [ "$max" = "eq" ]; then | |
| if [ "$n" -ne "$min" ]; then | |
| error " Pattern '$pat' expected $min times but got $n times" | |
| return 1 | |
| fi | |
| continue | |
| fi | |
| if [ "$min" != '-' ]; then | |
| if [ "$n" -lt "$min" ]; then | |
| error " Pattern '$pat' expected min $min times but got $n times" | |
| return 1 | |
| fi | |
| fi | |
| if [ "$max" != '-' ]; then | |
| if [ "$n" -gt "$max" ]; then | |
| error " Pattern '$pat' expected max $max times but got $n times" | |
| return 1 | |
| fi | |
| fi | |
| done | |
| return $? | |
| } | |
| ## | |
| match_patterns() | |
| { | |
| ifile="$1" | |
| patt="$2" | |
| ofile="$3" | |
| grep "$patt" "$ifile" | sed -e "s/^.*$patt(\(.*\)): *\(.*\)$/\1 \2/" | \ | |
| while read ins pat; do | |
| grep -s "^ $ins\\.*[0-9]* " "$ofile" | grep -v -s -q "$pat" | |
| if [ "$?" -ne 1 ]; then | |
| error " IR doesn't match '$pat'" | |
| return 1 | |
| fi | |
| done | |
| return $? | |
| } | |
| ## | |
| return_patterns() | |
| { | |
| ifile="$1" | |
| patt="$2" | |
| ofile="$3" | |
| grep "$patt:" "$ifile" | sed -e "s/^.*$patt: *\(.*\)$/\1/" | \ | |
| while read ret; do | |
| grep -s "^ ret\\.[0-9]" "$ofile" | grep -v -s -q "[ \$]${ret}\$" | |
| if [ "$?" -ne 1 ]; then | |
| error " Return doesn't match '$ret'" | |
| return 1 | |
| fi | |
| done | |
| return $? | |
| } | |
| ## | |
| # arg_file(filename) - checks if filename exists | |
| arg_file() | |
| { | |
| [ -z "$1" ] && { | |
| do_usage | |
| exit 1 | |
| } | |
| [ -e "$1" ] || { | |
| error "Can't open file $1" | |
| exit 1 | |
| } | |
| return 0 | |
| } | |
| ## | |
| do_usage() | |
| { | |
| echo "$prog_name - a tiny automatic testing script" | |
| echo "Usage: $prog_name [option(s)] [command] [arguments]" | |
| echo | |
| echo "options:" | |
| echo " -a|--abort Abort the tests as soon as one fails." | |
| echo " -q|--quiet Be extra quiet while running the tests." | |
| echo " --args='...' Add these options to the test command." | |
| echo | |
| echo "commands:" | |
| echo " [file ...] Runs the test suite on the given file(s)." | |
| echo " If a directory is given, run only those files." | |
| echo " If no file is given, run the whole testsuite." | |
| echo " single file Run the test in 'file'." | |
| echo " format file [name [cmd]] Help writing a new test case using cmd." | |
| echo | |
| echo " [command] help Print usage." | |
| } | |
| disable() | |
| { | |
| disabled_tests=$(($disabled_tests + 1)) | |
| if [ -z "$vquiet" ]; then | |
| echo " SKIP $1 ($2)" | |
| fi | |
| } | |
| ## | |
| # do_test(file) - tries to validate a test case | |
| # | |
| # it "parses" file, looking for check-* tags and tries to validate | |
| # the test against an expected result | |
| # returns: | |
| # - 0 if the test passed, | |
| # - 1 if it failed, | |
| # - 2 if it is not a "test-suite" test. | |
| # - 3 if the test is disabled. | |
| do_test() | |
| { | |
| test_failed=0 | |
| file="$1" | |
| quiet=0 | |
| get_tag_value $file | |
| # can this test be handled by test-suite ? | |
| # (it has to have a check-name key in it) | |
| if [ "$check_name" = "" ]; then | |
| warning "$file: test unhandled" | |
| unhandled_tests=$(($unhandled_tests + 1)) | |
| return 2 | |
| fi | |
| test_name="$check_name" | |
| # does the test provide a specific command ? | |
| if [ "$check_command" = "" ]; then | |
| check_command="$defaut_command" | |
| fi | |
| # check for disabled commands | |
| set -- $check_command | |
| base_cmd=$1 | |
| for i in $disabled_cmds; do | |
| if [ "$i" = "$base_cmd" ] ; then | |
| disable "$test_name" "$file" | |
| return 3 | |
| fi | |
| done | |
| if [ "$check_arch_ignore" != "" ]; then | |
| if echo $arch | egrep -q -w "$check_arch_ignore"; then | |
| disable "$test_name" "$file" | |
| return 3 | |
| fi | |
| fi | |
| if [ "$check_arch_only" != "" ]; then | |
| if ! (echo $arch | egrep -q -w "$check_arch_only"); then | |
| disable "$test_name" "$file" | |
| return 3 | |
| fi | |
| fi | |
| if [ "$check_assert" != "" ]; then | |
| res=$(../sparse - 2>&1 >/dev/null <<- EOF | |
| _Static_assert($check_assert, "$check_assert"); | |
| EOF | |
| ) | |
| if [ "$res" != "" ]; then | |
| disable "$test_name" "$file" | |
| return 3 | |
| fi | |
| fi | |
| if [ "$check_cpp_if" != "" ]; then | |
| res=$(../sparse -E - 2>/dev/null <<- EOF | |
| #if !($check_cpp_if) | |
| fail | |
| #endif | |
| EOF | |
| ) | |
| if [ "$res" != "" ]; then | |
| disable "$test_name" "$file" | |
| return 3 | |
| fi | |
| fi | |
| if [ -z "$vquiet" ]; then | |
| echo " TEST $test_name ($file)" | |
| fi | |
| verbose "Using command : $(echo "$@")" | |
| # grab the expected exit value | |
| expected_exit_value=$check_exit_value | |
| verbose "Expecting exit value: $expected_exit_value" | |
| # do we want a timeout? | |
| pre_cmd="" | |
| if [ $check_timeout -ne 0 ]; then | |
| pre_cmd="timeout $check_timeout" | |
| fi | |
| shift | |
| # launch the test command and | |
| # grab the actual output & exit value | |
| eval $pre_cmd $default_path/$base_cmd $default_args "$@" \ | |
| 1> $file.output.got 2> $file.error.got | |
| actual_exit_value=$? | |
| must_fail=$check_known_to_fail | |
| [ $must_fail -eq 1 ] && [ $V -eq 0 ] && quiet=1 | |
| known_ko_tests=$(($known_ko_tests + $must_fail)) | |
| for stream in error output; do | |
| eval ignore=\$check_${stream}_ignore | |
| [ $ignore -eq 1 ] && continue | |
| # grab the expected output | |
| sed -n "/check-$stream-start/,/check-$stream-end/p" $file \ | |
| | grep -v check-$stream > "$file".$stream.expected | |
| diff -u "$file".$stream.expected "$file".$stream.got > "$file".$stream.diff | |
| if [ "$?" -ne "0" ]; then | |
| error "actual $stream text does not match expected $stream text." | |
| error "see $file.$stream.* for further investigation." | |
| [ $quiet -ne 1 ] && cat "$file".$stream.diff | |
| test_failed=1 | |
| fi | |
| done | |
| if [ "$actual_exit_value" -ne "$expected_exit_value" ]; then | |
| error "Actual exit value does not match the expected one." | |
| error "expected $expected_exit_value, got $actual_exit_value." | |
| test_failed=1 | |
| fi | |
| # verify the 'check-output-contains/excludes' tags | |
| if [ $check_output_contains -eq 1 ]; then | |
| has_each_patterns "$file" 'check-output-contains' absent $file.output.got | |
| if [ "$?" -ne "0" ]; then | |
| test_failed=1 | |
| fi | |
| fi | |
| if [ $check_output_excludes -eq 1 ]; then | |
| has_none_patterns "$file" 'check-output-excludes' present $file.output.got | |
| if [ "$?" -ne "0" ]; then | |
| test_failed=1 | |
| fi | |
| fi | |
| if [ $check_output_pattern -eq 1 ]; then | |
| # verify the 'check-output-pattern(...)' tags | |
| minmax_patterns "$file" 'check-output-pattern' $file.output.got | |
| if [ "$?" -ne "0" ]; then | |
| test_failed=1 | |
| fi | |
| fi | |
| if [ $check_output_match -eq 1 ]; then | |
| # verify the 'check-output-match($insn): $patt' tags | |
| match_patterns "$file" 'check-output-match' $file.output.got | |
| if [ "$?" -ne "0" ]; then | |
| test_failed=1 | |
| fi | |
| fi | |
| if [ $check_output_returns -eq 1 ]; then | |
| # verify the 'check-output-return: $value' tags | |
| return_patterns "$file" 'check-output-returns' $file.output.got | |
| if [ "$?" -ne "0" ]; then | |
| test_failed=1 | |
| fi | |
| fi | |
| if [ "$must_fail" -eq "1" ]; then | |
| if [ "$test_failed" -eq "1" ]; then | |
| [ -z "$vquiet" ] && \ | |
| echo "info: XFAIL: test '$file' is known to fail" | |
| else | |
| echo "error: XPASS: test '$file' is known to fail but succeed!" | |
| fi | |
| else | |
| if [ "$test_failed" -eq "1" ]; then | |
| echo "error: FAIL: test '$file' failed" | |
| else | |
| [ "$V" -ne "0" ] && \ | |
| echo "info: PASS: test '$file' passed" | |
| fi | |
| fi | |
| if [ "$test_failed" -ne "$must_fail" ]; then | |
| [ $abort -eq 1 ] && exit 1 | |
| test_failed=1 | |
| failed=1 | |
| fi | |
| if [ "$test_failed" -eq "1" ]; then | |
| ko_tests=$(($ko_tests + 1)) | |
| else | |
| ok_tests=$(($ok_tests + 1)) | |
| rm -f $file.{error,output}.{expected,got,diff} | |
| fi | |
| return $test_failed | |
| } | |
| do_test_suite() | |
| { | |
| for i in $tests_list; do | |
| do_test "$i" | |
| done | |
| OK=OK | |
| [ $failed -eq 0 ] || OK=KO | |
| # prints some numbers | |
| tests_nr=$(($ok_tests + $ko_tests)) | |
| echo "$OK: out of $tests_nr tests, $ok_tests passed, $ko_tests failed" | |
| if [ "$known_ko_tests" -ne 0 ]; then | |
| echo " $known_ko_tests of them are known to fail" | |
| fi | |
| if [ "$unhandled_tests" -ne "0" ]; then | |
| echo " $unhandled_tests tests could not be handled by $prog_name" | |
| fi | |
| if [ "$disabled_tests" -ne "0" ]; then | |
| echo " $disabled_tests tests were disabled" | |
| fi | |
| } | |
| ## | |
| do_format_help() { | |
| echo "Usage: $prog_name [option(s)] [--]format file [name [cmd]]" | |
| echo | |
| echo "options:" | |
| echo " -a append the created test to the input file" | |
| echo " -f write a test known to fail" | |
| echo " -l write a test for linearized code" | |
| echo " -r write a test for linearized code returning 1" | |
| echo " -p write a test for pre-processing" | |
| echo " -s write a test for symbolic checking" | |
| echo | |
| echo "argument(s):" | |
| echo " file file containing the test case(s)" | |
| echo " name name for the test case (defaults to file)" | |
| echo " cmd command to be used (defaults to 'sparse \$file')" | |
| } | |
| ## | |
| # do_format([options,] file[, name[, cmd]]) - helps a test writer to format test-suite tags | |
| do_format() | |
| { | |
| def_cmd="$default_cmd" | |
| append=0 | |
| linear=0 | |
| fail=0 | |
| ret='' | |
| while [ $# -gt 0 ] ; do | |
| case "$1" in | |
| -a) | |
| append=1 ;; | |
| -f) | |
| fail=1 ;; | |
| -l) | |
| def_cmd='test-linearize -Wno-decl $file' | |
| linear=1 ;; | |
| -r) | |
| def_cmd='test-linearize -Wno-decl $file' | |
| ret=1 ;; | |
| -p) | |
| def_cmd='sparse -E $file' ;; | |
| -s) | |
| def_cmd='scheck $file' ;; | |
| help|-*) | |
| do_format_help | |
| return 0 | |
| ;; | |
| *) break ;; | |
| esac | |
| shift | |
| continue | |
| done | |
| if [ $# -lt 1 -o $# -gt 3 ]; then | |
| do_format_help | |
| return 0 | |
| fi | |
| arg_file "$1" || return 1 | |
| file="$1" | |
| fname="$2" | |
| [ -z "$fname" ] && fname="$(basename "$1" .c)" | |
| fcmd="$3" | |
| [ -z "$fcmd" ] && fcmd="$def_cmd" | |
| cmd=`eval echo $default_path/$fcmd` | |
| $cmd 1> $file.output.got 2> $file.error.got | |
| fexit_value=$? | |
| [ $append != 0 ] && exec >> $file | |
| cat <<_EOF | |
| /* | |
| * check-name: $fname | |
| _EOF | |
| if [ "$fcmd" != "$default_cmd" ]; then | |
| echo " * check-command: $fcmd" | |
| fi | |
| if [ "$fexit_value" -ne "0" ]; then | |
| echo " * check-exit-value: $fexit_value" | |
| fi | |
| if [ $fail != 0 ]; then | |
| echo " * check-known-to-fail" | |
| fi | |
| if [ "$ret" != '' ]; then | |
| echo ' *' | |
| echo ' * check-output-ignore' | |
| echo " * check-output-returns: $ret" | |
| rm -f "$file.output.got" | |
| fi | |
| if [ $linear != 0 ]; then | |
| echo ' *' | |
| echo ' * check-output-ignore' | |
| echo ' * check-output-contains: xyz\\\\.' | |
| echo ' * check-output-excludes: \\\\.' | |
| fi | |
| for stream in output error; do | |
| if [ -s "$file.$stream.got" ]; then | |
| echo " *" | |
| echo " * check-$stream-start" | |
| cat "$file.$stream.got" | |
| echo " * check-$stream-end" | |
| fi | |
| done | |
| echo " */" | |
| return 0 | |
| } | |
| ## allow flags from environment | |
| set -- $SPARSE_TEST_FLAGS "$@" | |
| ## process the flags | |
| while [ "$#" -gt "0" ]; do | |
| case "$1" in | |
| -a|--abort) | |
| abort=1 | |
| ;; | |
| -q|--quiet) | |
| vquiet=1 | |
| ;; | |
| --args=*) | |
| default_args="${1#--args=}"; | |
| ;; | |
| single|--single) | |
| arg_file "$2" | |
| do_test "$2" | |
| case "$?" in | |
| 0) echo "$2 passed !";; | |
| 1) echo "$2 failed !";; | |
| 2) echo "$2 can't be handled by $prog_name";; | |
| esac | |
| exit $failed | |
| ;; | |
| format|--format) | |
| shift | |
| do_format "$@" | |
| exit 0 | |
| ;; | |
| help) | |
| do_usage | |
| exit 1 | |
| ;; | |
| *.c|*.cdoc) | |
| tests_list="$tests_list $1" | |
| ;; | |
| *) | |
| if [ ! -d "$1" ]; then | |
| do_usage | |
| exit 1 | |
| fi | |
| tests_list="$tests_list $(find "$1" -name '*.c' | sort)" | |
| ;; | |
| esac | |
| shift | |
| done | |
| if [ -z "$tests_list" ]; then | |
| tests_list=`find . -name '*.c' | sed -e 's#^\./\(.*\)#\1#' | sort` | |
| fi | |
| do_test_suite | |
| exit $failed | |