#!/bin/sh # # Copyright (c) 2011-2016 Dmitry V. Levin <ldv@altlinux.org> # Copyright (c) 2011-2018 The strace developers. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. export LC_ALL=C ME_="${0##*/}" LOG="log" OUT="out" EXP="exp" warn_() { printf >&2 '%s\n' "$*"; } fail_() { warn_ "$ME_: failed test: $*"; exit 1; } skip_() { warn_ "$ME_: skipped test: $*"; exit 77; } framework_failure_() { warn_ "$ME_: framework failure: $*"; exit 99; } framework_skip_() { warn_ "$ME_: framework skip: $*"; exit 77; } check_prog() { type "$@" > /dev/null 2>&1 || framework_skip_ "$* is not available" } dump_log_and_fail_with() { cat < "$LOG" >&2 fail_ "$*" } run_prog() { if [ $# -eq 0 ]; then set -- "../$NAME" fi args="$*" "$@" || { rc=$? if [ $rc -eq 77 ]; then skip_ "$args exited with code 77" else fail_ "$args failed with code $rc" fi } } run_prog_skip_if_failed() { args="$*" "$@" || framework_skip_ "$args failed with code $?" } try_run_prog() { local rc "$@" > /dev/null || { rc=$? if [ $rc -eq 77 ]; then return 1 else fail_ "$* failed with code $rc" fi } } run_strace() { > "$LOG" || fail_ "failed to write $LOG" args="$*" $STRACE -o "$LOG" "$@" || dump_log_and_fail_with "$STRACE $args failed with code $?" } run_strace_merge() { rm -f -- "$LOG".[0-9]* run_strace -ff -tt "$@" "$srcdir"/../strace-log-merge "$LOG" > "$LOG" || dump_log_and_fail_with 'strace-log-merge failed with code $?' rm -f -- "$LOG".[0-9]* } check_gawk() { check_prog gawk check_prog grep local program="$1"; shift if grep '^@include[[:space:]]' < "$program" > /dev/null; then gawk '@include "/dev/null"' < /dev/null || framework_skip_ 'gawk does not support @include' fi } # Usage: [FILE_TO_CHECK [AWK_PROGRAM [ERROR_MESSAGE [EXTRA_AWK_OPTIONS...]]]] # Check whether AWK_PROGRAM matches FILE_TO_CHECK using gawk. # If it doesn't, dump FILE_TO_CHECK and fail with ERROR_MESSAGE. match_awk() { local output program error if [ $# -eq 0 ]; then output="$LOG" else output="$1"; shift fi if [ $# -eq 0 ]; then program="$srcdir/$NAME.awk" else program="$1"; shift fi if [ $# -eq 0 ]; then error="$STRACE $args output mismatch" else error="$1"; shift fi check_gawk "$program" AWKPATH="$srcdir" gawk -f "$program" "$@" < "$output" || { cat < "$output" fail_ "$error" } } # Usage: [FILE_TO_CHECK [FILE_TO_COMPATE_WITH [ERROR_MESSAGE]]] # Check whether FILE_TO_CHECK differs from FILE_TO_COMPATE_WITH. # If it does, dump the difference and fail with ERROR_MESSAGE. match_diff() { local output expected error if [ $# -eq 0 ]; then output="$LOG" else output="$1"; shift fi if [ $# -eq 0 ]; then expected="$srcdir/$NAME.expected" else expected="$1"; shift fi if [ $# -eq 0 ]; then error="$STRACE $args output mismatch" else error="$1"; shift fi check_prog diff diff -u -- "$expected" "$output" || fail_ "$error" } # Usage: [FILE_TO_CHECK [FILE_WITH_PATTERNS [ERROR_MESSAGE]]] # Check whether all patterns listed in FILE_WITH_PATTERNS # match FILE_TO_CHECK using egrep. # If at least one of these patterns does not match, # dump both files and fail with ERROR_MESSAGE. match_grep() { local output patterns error pattern cnt failed= if [ $# -eq 0 ]; then output="$LOG" else output="$1"; shift fi if [ $# -eq 0 ]; then patterns="$srcdir/$NAME.expected" else patterns="$1"; shift fi if [ $# -eq 0 ]; then error="$STRACE $args output mismatch" else error="$1"; shift fi check_prog wc check_prog grep cnt=1 while read -r pattern; do LC_ALL=C grep -E -x -e "$pattern" < "$output" > /dev/null || { test -n "$failed" || { echo 'Failed patterns of expected output:' failed=1 } printf '#%d: %s\n' "$cnt" "$pattern" } cnt=$(($cnt + 1)) done < "$patterns" test -z "$failed" || { echo 'Actual output:' cat < "$output" fail_ "$error" } } # Usage: run_strace_match_diff [args to run_strace] run_strace_match_diff() { args="$*" [ -n "$args" -a -z "${args##*-e trace=*}" ] || set -- -e trace="$NAME" "$@" run_prog > /dev/null run_strace "$@" $args > "$EXP" match_diff "$LOG" "$EXP" } # Usage: run_strace_match_grep [args to run_strace] run_strace_match_grep() { args="$*" [ -n "$args" -a -z "${args##*-e trace=*}" ] || set -- -e trace="$NAME" "$@" run_prog > /dev/null run_strace "$@" $args > "$EXP" match_grep "$LOG" "$EXP" } # Print kernel version code. # usage: kernel_version_code $(uname -r) kernel_version_code() { ( set -f IFS=. set -- $1 0 0 v1="${1%%[!0-9]*}" && [ -n "$v1" ] || v1=0 v2="${2%%[!0-9]*}" && [ -n "$v2" ] || v2=0 v3="${3%%[!0-9]*}" && [ -n "$v3" ] || v3=0 echo "$(($v1 * 65536 + $v2 * 256 + $v3))" ) } # Usage: require_min_kernel_version_or_skip 3.0 require_min_kernel_version_or_skip() { local uname_r uname_r="$(uname -r)" [ "$(kernel_version_code "$uname_r")" -ge \ "$(kernel_version_code "$1")" ] || skip_ "the kernel release $uname_r is not $1 or newer" } # Usage: grep_pid_status $pid GREP-OPTIONS... grep_pid_status() { local pid pid=$1; shift cat < "/proc/$pid/status" | grep "$@" } # Subtracts one program set from another. # If an optional regular expression is specified, the lines in the minuend file # that match this regular expression are elso excluded from the output. # # Usage: prog_set_subtract minuend_file subtrahend_file [subtrahend_regexp] prog_set_subtract() { local min sub re pat min="$1"; shift sub="$1"; shift re="${1-}" pat="$re|$(sed 's/[[:space:]].*//' < "$sub" | tr -s '\n' '|')" grep -E -v -x -e "$pat" < "$min" } # Usage: test_pure_prog_set [--expfile FILE] COMMON_ARGS < tests_file # stdin should consist of lines in "test_name strace_args..." format. test_pure_prog_set() { local expfile expfile="$EXP" while [ -n "$1" ]; do case "$1" in --expfile) shift expfile="$1" shift ;; *) break ;; esac done while read -r t prog_args; do { # skip lines beginning with "#" symbol [ "${t###}" = "$t" ] || continue try_run_prog "../$t" || continue run_strace $prog_args "$@" "../$t" > "$expfile" match_diff "$LOG" "$expfile" } < /dev/null; done } # Run strace against list of programs put in "$NAME.in" and then against the # rest of pure_executables.list with the expectation of empty output in the # latter case. # # Usage: source this file after init.sh and call: # test_trace_expr subtrahend_regexp strace_args # Environment: # $NAME: test name, used for "$NAME.in" file containing list of tests # for positive trace expression match; # $srcdir: used to find pure_executables.list and "$NAME.in" files. # Files created: # negative.list: File containing list of tests for negative match. test_trace_expr() { local subtrahend_regexp subtrahend_regexp="$1"; shift test_pure_prog_set "$@" < "$srcdir/$NAME.in" prog_set_subtract "$srcdir/pure_executables.list" "$srcdir/$NAME.in" \ "$subtrahend_regexp" > negative.list test_pure_prog_set --expfile /dev/null -qq -esignal=none "$@" \ < negative.list } check_prog cat check_prog rm case "$ME_" in *.gen.test) NAME="${ME_%.gen.test}" ;; *.test) NAME="${ME_%.test}" ;; *) NAME= esac STRACE_EXE= if [ -n "$NAME" ]; then TESTDIR="$NAME.dir" rm -rf -- "$TESTDIR" mkdir -- "$TESTDIR" cd "$TESTDIR" case "$srcdir" in /*) ;; *) srcdir="../$srcdir" ;; esac [ -n "${STRACE-}" ] || { STRACE=../../strace case "${LOG_COMPILER-} ${LOG_FLAGS-}" in *--suppressions=*--error-exitcode=*--tool=*) STRACE_EXE="$STRACE" # add valgrind command prefix STRACE="${LOG_COMPILER-} ${LOG_FLAGS-} $STRACE" ;; esac } trap 'dump_log_and_fail_with "time limit ($TIMEOUT_DURATION) exceeded"' XCPU else : "${STRACE:=../strace}" fi # Export $STRACE_EXE to check_PROGRAMS. : "${STRACE_EXE:=$STRACE}" export STRACE_EXE : "${TIMEOUT_DURATION:=600}" : "${SLEEP_A_BIT:=sleep 1}" [ -z "${VERBOSE-}" ] || set -x