1#!/bin/sh
2#
3# Copyright (c) 2011-2016 Dmitry V. Levin <ldv@altlinux.org>
4# Copyright (c) 2011-2017 The strace developers.
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15# 3. The name of the author may not be used to endorse or promote products
16#    derived from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29ME_="${0##*/}"
30LOG="log"
31OUT="out"
32EXP="exp"
33
34warn_() { printf >&2 '%s\n' "$*"; }
35fail_() { warn_ "$ME_: failed test: $*"; exit 1; }
36skip_() { warn_ "$ME_: skipped test: $*"; exit 77; }
37framework_failure_() { warn_ "$ME_: framework failure: $*"; exit 99; }
38framework_skip_() { warn_ "$ME_: framework skip: $*"; exit 77; }
39
40check_prog()
41{
42	type "$@" > /dev/null 2>&1 ||
43		framework_skip_ "$* is not available"
44}
45
46dump_log_and_fail_with()
47{
48	cat < "$LOG"
49	fail_ "$*"
50}
51
52run_prog()
53{
54	if [ $# -eq 0 ]; then
55		set -- "../$NAME"
56	fi
57	args="$*"
58	"$@" || {
59		rc=$?
60		if [ $rc -eq 77 ]; then
61			skip_ "$args exited with code 77"
62		else
63			fail_ "$args failed with code $rc"
64		fi
65	}
66}
67
68
69run_prog_skip_if_failed()
70{
71	args="$*"
72	"$@" || framework_skip_ "$args failed with code $?"
73}
74
75try_run_prog()
76{
77	local rc
78
79	"$@" > /dev/null || {
80		rc=$?
81		if [ $rc -eq 77 ]; then
82			return 1
83		else
84			fail_ "$* failed with code $rc"
85		fi
86	}
87}
88
89run_strace()
90{
91	> "$LOG" || fail_ "failed to write $LOG"
92	args="$*"
93	$STRACE -o "$LOG" "$@" ||
94		dump_log_and_fail_with "$STRACE $args failed with code $?"
95}
96
97run_strace_merge()
98{
99	rm -f -- "$LOG".[0-9]*
100	run_strace -ff -tt "$@"
101	"$srcdir"/../strace-log-merge "$LOG" > "$LOG" ||
102		dump_log_and_fail_with 'strace-log-merge failed with code $?'
103	rm -f -- "$LOG".[0-9]*
104}
105
106check_gawk()
107{
108	check_prog gawk
109	check_prog grep
110
111	local program="$1"; shift
112	if grep '^@include[[:space:]]' < "$program" > /dev/null; then
113		gawk '@include "/dev/null"' < /dev/null ||
114			framework_skip_ 'gawk does not support @include'
115	fi
116}
117
118# Usage: [FILE_TO_CHECK [AWK_PROGRAM [ERROR_MESSAGE [EXTRA_AWK_OPTIONS...]]]]
119# Check whether AWK_PROGRAM matches FILE_TO_CHECK using gawk.
120# If it doesn't, dump FILE_TO_CHECK and fail with ERROR_MESSAGE.
121match_awk()
122{
123	local output program error
124	if [ $# -eq 0 ]; then
125		output="$LOG"
126	else
127		output="$1"; shift
128	fi
129	if [ $# -eq 0 ]; then
130		program="$srcdir/$NAME.awk"
131	else
132		program="$1"; shift
133	fi
134	if [ $# -eq 0 ]; then
135		error="$STRACE $args output mismatch"
136	else
137		error="$1"; shift
138	fi
139
140	check_gawk "$program"
141
142	AWKPATH="$srcdir" gawk -f "$program" "$@" < "$output" || {
143		cat < "$output"
144		fail_ "$error"
145	}
146}
147
148# Usage: [FILE_TO_CHECK [FILE_TO_COMPATE_WITH [ERROR_MESSAGE]]]
149# Check whether FILE_TO_CHECK differs from FILE_TO_COMPATE_WITH.
150# If it does, dump the difference and fail with ERROR_MESSAGE.
151match_diff()
152{
153	local output expected error
154	if [ $# -eq 0 ]; then
155		output="$LOG"
156	else
157		output="$1"; shift
158	fi
159	if [ $# -eq 0 ]; then
160		expected="$srcdir/$NAME.expected"
161	else
162		expected="$1"; shift
163	fi
164	if [ $# -eq 0 ]; then
165		error="$STRACE $args output mismatch"
166	else
167		error="$1"; shift
168	fi
169
170	check_prog diff
171
172	diff -u -- "$expected" "$output" ||
173		fail_ "$error"
174}
175
176# Usage: [FILE_TO_CHECK [FILE_WITH_PATTERNS [ERROR_MESSAGE]]]
177# Check whether all patterns listed in FILE_WITH_PATTERNS
178# match FILE_TO_CHECK using egrep.
179# If at least one of these patterns does not match,
180# dump both files and fail with ERROR_MESSAGE.
181match_grep()
182{
183	local output patterns error pattern cnt failed=
184	if [ $# -eq 0 ]; then
185		output="$LOG"
186	else
187		output="$1"; shift
188	fi
189	if [ $# -eq 0 ]; then
190		patterns="$srcdir/$NAME.expected"
191	else
192		patterns="$1"; shift
193	fi
194	if [ $# -eq 0 ]; then
195		error="$STRACE $args output mismatch"
196	else
197		error="$1"; shift
198	fi
199
200	check_prog wc
201	check_prog grep
202
203	cnt=1
204	while read -r pattern; do
205		LC_ALL=C grep -E -x -e "$pattern" < "$output" > /dev/null || {
206			test -n "$failed" || {
207				echo 'Failed patterns of expected output:'
208				failed=1
209			}
210			printf '#%d: %s\n' "$cnt" "$pattern"
211		}
212		cnt=$(($cnt + 1))
213	done < "$patterns"
214	test -z "$failed" || {
215		echo 'Actual output:'
216		cat < "$output"
217		fail_ "$error"
218	}
219}
220
221# Usage: run_strace_match_diff [args to run_strace]
222run_strace_match_diff()
223{
224	args="$*"
225	[ -n "$args" -a -z "${args##*-e trace=*}" ] ||
226		set -- -e trace="$NAME" "$@"
227	run_prog > /dev/null
228	run_strace "$@" $args > "$EXP"
229	match_diff "$LOG" "$EXP"
230}
231
232# Print kernel version code.
233# usage: kernel_version_code $(uname -r)
234kernel_version_code()
235{
236	(
237		set -f
238		IFS=.
239		set -- $1
240		v1="${1%%[!0-9]*}" && [ -n "$v1" ] || v1=0
241		v2="${2%%[!0-9]*}" && [ -n "$v2" ] || v2=0
242		v3="${3%%[!0-9]*}" && [ -n "$v3" ] || v3=0
243		echo "$(($v1 * 65536 + $v2 * 256 + $v3))"
244	)
245}
246
247# Usage: require_min_kernel_version_or_skip 3.0
248require_min_kernel_version_or_skip()
249{
250	local uname_r
251	uname_r="$(uname -r)"
252
253	[ "$(kernel_version_code "$uname_r")" -ge \
254	  "$(kernel_version_code "$1")" ] ||
255		skip_ "the kernel release $uname_r is not $1 or newer"
256}
257
258# Usage: grep_pid_status $pid GREP-OPTIONS...
259grep_pid_status()
260{
261	local pid
262	pid=$1; shift
263	cat < "/proc/$pid/status" | grep "$@"
264}
265
266# Subtracts one program set from another.
267# If an optional regular expression is specified, the lines in the minuend file
268# that match this regular expression are elso excluded from the output.
269#
270# Usage: prog_set_subtract minuend_file subtrahend_file [subtrahend_regexp]
271prog_set_subtract()
272{
273	local min sub re pat
274	min="$1"; shift
275	sub="$1"; shift
276	re="${1-}"
277	pat="$re|$(sed 's/[[:space:]].*//' < "$sub" | tr -s '\n' '|')"
278	grep -E -v -x -e "$pat" < "$min"
279}
280
281# Usage: test_pure_prog_set [--expfile FILE] COMMON_ARGS < tests_file
282# stdin should consist of lines in "test_name strace_args..." format.
283test_pure_prog_set()
284{
285	local expfile
286
287	expfile="$EXP"
288
289	while [ -n "$1" ]; do
290		case "$1" in
291		--expfile)
292			shift
293			expfile="$1"
294			shift
295			;;
296		*)
297			break
298			;;
299		esac
300	done
301
302	while read -r t prog_args; do {
303		# skip lines beginning with "#" symbol
304		[ "${t###}" = "$t" ] || continue
305
306		try_run_prog "../$t" || continue
307		run_strace $prog_args "$@" "../$t" > "$expfile"
308		match_diff "$LOG" "$expfile"
309	} < /dev/null; done
310}
311
312# Run strace against list of programs put in "$NAME.in" and then against the
313# rest of pure_executables.list with the expectation of empty output in the
314# latter case.
315#
316# Usage: source this file after init.sh and call:
317#   test_trace_expr subtrahend_regexp strace_args
318# Environment:
319#   $NAME:	test name, used for "$NAME.in" file containing list of tests
320#		for positive trace expression match;
321#   $srcdir:	used to find pure_executables.list and "$NAME.in" files.
322# Files created:
323#   negative.list: File containing list of tests for negative match.
324test_trace_expr()
325{
326	local subtrahend_regexp
327	subtrahend_regexp="$1"; shift
328	test_pure_prog_set "$@" < "$srcdir/$NAME.in"
329	prog_set_subtract "$srcdir/pure_executables.list" "$srcdir/$NAME.in" \
330		"$subtrahend_regexp" > negative.list
331	test_pure_prog_set --expfile /dev/null -qq -esignal=none "$@" \
332		< negative.list
333}
334
335check_prog cat
336check_prog rm
337
338case "$ME_" in
339	*.gen.test) NAME="${ME_%.gen.test}" ;;
340	*.test) NAME="${ME_%.test}" ;;
341	*) NAME=
342esac
343
344if [ -n "$NAME" ]; then
345	TESTDIR="$NAME.dir"
346	rm -rf -- "$TESTDIR"
347	mkdir -- "$TESTDIR"
348	cd "$TESTDIR"
349
350	case "$srcdir" in
351		/*) ;;
352		*) srcdir="../$srcdir" ;;
353	esac
354
355	[ -n "${STRACE-}" ] || {
356		STRACE=../../strace
357		case "${LOG_COMPILER-} ${LOG_FLAGS-}" in
358			*--suppressions=*--error-exitcode=*--tool=*)
359			# add valgrind command prefix
360			STRACE="${LOG_COMPILER-} ${LOG_FLAGS-} $STRACE"
361			;;
362		esac
363	}
364else
365	[ -n "${STRACE-}" ] ||
366		STRACE=../strace
367fi
368
369: "${TIMEOUT_DURATION:=300}"
370: "${SLEEP_A_BIT:=sleep 1}"
371
372[ -z "${VERBOSE-}" ] ||
373	set -x
374