1#!/bin/sh
2#
3# Copyright (c) 2011-2016 Dmitry V. Levin <ldv@altlinux.org>
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14# 3. The name of the author may not be used to endorse or promote products
15#    derived from this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28ME_="${0##*/}"
29LOG="$ME_.tmp"
30OUT="$LOG.out"
31EXP="$LOG.exp"
32NAME="${ME_%.test}"
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
75run_strace()
76{
77	> "$LOG" || fail_ "failed to write $LOG"
78	args="$*"
79	$STRACE -o "$LOG" "$@" ||
80		dump_log_and_fail_with "$STRACE $args failed with code $?"
81}
82
83run_strace_merge()
84{
85	rm -f -- "$LOG".[0-9]*
86	run_strace -ff -tt "$@"
87	"$srcdir"/../strace-log-merge "$LOG" > "$LOG" ||
88		dump_log_and_fail_with 'strace-log-merge failed with code $?'
89	rm -f -- "$LOG".[0-9]*
90}
91
92check_gawk()
93{
94	check_prog gawk
95	check_prog grep
96
97	local program="$1"; shift
98	if grep '^@include[[:space:]]' < "$program" > /dev/null; then
99		gawk '@include "/dev/null"' < /dev/null ||
100			framework_skip_ 'gawk does not support @include'
101	fi
102}
103
104# Usage: [FILE_TO_CHECK [AWK_PROGRAM [ERROR_MESSAGE [EXTRA_AWK_OPTIONS...]]]]
105# Check whether AWK_PROGRAM matches FILE_TO_CHECK using gawk.
106# If it doesn't, dump FILE_TO_CHECK and fail with ERROR_MESSAGE.
107match_awk()
108{
109	local output program error
110	if [ $# -eq 0 ]; then
111		output="$LOG"
112	else
113		output="$1"; shift
114	fi
115	if [ $# -eq 0 ]; then
116		program="$srcdir/$NAME.awk"
117	else
118		program="$1"; shift
119	fi
120	if [ $# -eq 0 ]; then
121		error="$STRACE $args output mismatch"
122	else
123		error="$1"; shift
124	fi
125
126	check_gawk "$program"
127
128	AWKPATH="$srcdir" gawk -f "$program" "$@" < "$output" || {
129		cat < "$output"
130		fail_ "$error"
131	}
132}
133
134# Usage: [FILE_TO_CHECK [FILE_TO_COMPATE_WITH [ERROR_MESSAGE]]]
135# Check whether FILE_TO_CHECK differs from FILE_TO_COMPATE_WITH.
136# If it does, dump the difference and fail with ERROR_MESSAGE.
137match_diff()
138{
139	local output expected error
140	if [ $# -eq 0 ]; then
141		output="$LOG"
142	else
143		output="$1"; shift
144	fi
145	if [ $# -eq 0 ]; then
146		expected="$srcdir/$NAME.expected"
147	else
148		expected="$1"; shift
149	fi
150	if [ $# -eq 0 ]; then
151		error="$STRACE $args output mismatch"
152	else
153		error="$1"; shift
154	fi
155
156	check_prog diff
157
158	diff -- "$expected" "$output" ||
159		fail_ "$error"
160}
161
162# Usage: [FILE_TO_CHECK [FILE_WITH_PATTERNS [ERROR_MESSAGE]]]
163# Check whether all patterns listed in FILE_WITH_PATTERNS
164# match FILE_TO_CHECK using egrep.
165# If at least one of these patterns does not match,
166# dump both files and fail with ERROR_MESSAGE.
167match_grep()
168{
169	local output patterns error pattern cnt failed=
170	if [ $# -eq 0 ]; then
171		output="$LOG"
172	else
173		output="$1"; shift
174	fi
175	if [ $# -eq 0 ]; then
176		patterns="$srcdir/$NAME.expected"
177	else
178		patterns="$1"; shift
179	fi
180	if [ $# -eq 0 ]; then
181		error="$STRACE $args output mismatch"
182	else
183		error="$1"; shift
184	fi
185
186	check_prog wc
187	check_prog grep
188
189	cnt=1
190	while read -r pattern; do
191		LC_ALL=C grep -E -x -e "$pattern" < "$output" > /dev/null || {
192			test -n "$failed" || {
193				echo 'Failed patterns of expected output:'
194				failed=1
195			}
196			printf '#%d: %s\n' "$cnt" "$pattern"
197		}
198		cnt=$(($cnt + 1))
199	done < "$patterns"
200	test -z "$failed" || {
201		echo 'Actual output:'
202		cat < "$output"
203		fail_ "$error"
204	}
205}
206
207# Usage: run_strace_match_diff [args to run_strace]
208run_strace_match_diff()
209{
210	args="$*"
211	[ -n "$args" -a -z "${args##*-e trace=*}" ] ||
212		set -- -e trace="$NAME" "$@"
213	run_prog > /dev/null
214	run_strace "$@" $args > "$EXP"
215	match_diff "$LOG" "$EXP"
216	rm -f "$EXP"
217}
218
219# Print kernel version code.
220# usage: kernel_version_code $(uname -r)
221kernel_version_code()
222{
223	(
224		set -f
225		IFS=.
226		set -- $1
227		v1="${1%%[!0-9]*}" && [ -n "$v1" ] || v1=0
228		v2="${2%%[!0-9]*}" && [ -n "$v2" ] || v2=0
229		v3="${3%%[!0-9]*}" && [ -n "$v3" ] || v3=0
230		echo "$(($v1 * 65536 + $v2 * 256 + $v3))"
231	)
232}
233
234# Usage: require_min_kernel_version_or_skip 3.0
235require_min_kernel_version_or_skip()
236{
237	local uname_r
238	uname_r="$(uname -r)"
239
240	[ "$(kernel_version_code "$uname_r")" -ge \
241	  "$(kernel_version_code "$1")" ] ||
242		skip_ "the kernel release $uname_r is not $1 or newer"
243}
244
245# Usage: grep_pid_status $pid GREP-OPTIONS...
246grep_pid_status()
247{
248	local pid
249	pid=$1; shift
250	cat < "/proc/$pid/status" | grep "$@"
251}
252
253check_prog cat
254check_prog rm
255
256rm -f "$LOG"
257
258[ -n "${STRACE-}" ] || {
259	STRACE=../strace
260	case "${LOG_COMPILER-} ${LOG_FLAGS-}" in
261		*--suppressions=*--error-exitcode=*--tool=*)
262			# add valgrind command prefix
263			STRACE="${LOG_COMPILER-} ${LOG_FLAGS-} $STRACE"
264			;;
265	esac
266}
267
268: "${TIMEOUT_DURATION:=60}"
269: "${SLEEP_A_BIT:=sleep 1}"
270
271[ -z "${VERBOSE-}" ] ||
272	set -x
273