32e3e92de7
When testing for specific output of shell tools, we need to ensure that the system locale (typically engineering English) is active, otherwise we end up with mismatches when the error messages get translated.
219 lines
7.3 KiB
Bash
Executable file
219 lines
7.3 KiB
Bash
Executable file
#! /bin/sh
|
|
#
|
|
# Wrapper script which runs a command under valgrind control,
|
|
# redirects valgrind output into a log file and then
|
|
# checks after the run whether valgrind errors or warnings
|
|
# were logged.
|
|
#
|
|
# The environment is set so that GNOME libs and libstdc++ are
|
|
# valgrind-friendly (for example, explicit alloc/free instead of
|
|
# pooling).
|
|
#
|
|
# Additional valgrind parameters can be passed in the VALGRIND_ARGS
|
|
# environment variable. The log file can be chosen via VALGRIND_LOG,
|
|
# with valgrind.<pid of shell>.<current process>.out as default.
|
|
|
|
|
|
if [ "$VALGRIND_LOG" ]; then
|
|
LOGPARAM=$VALGRIND_LOG
|
|
LOGFILES=$VALGRIND_LOG
|
|
CUSTOM_LOGPARAM=yes
|
|
else
|
|
LOGPARAM=$(pwd)/valgrind.p$$.c%p.out
|
|
# More than one file might get written, use wildcard.
|
|
LOGFILES=$(pwd)/valgrind.p$$.*.out
|
|
CUSTOM_LOGPARAM=no
|
|
fi
|
|
|
|
trap "cat $LOGFILES >&2 2>/dev/null; rm -f $LOGFILES" EXIT
|
|
|
|
# Finds all valgrind processes created for this run of
|
|
# valgrindcheck.sh, detected based on the log files created for
|
|
# them. This is necessary to catch processes which might have forked
|
|
# off from the $VALGRIND_PID that we know about. We cannot simply
|
|
# kill all valgrind processes, because there might be other, longer
|
|
# running processes that need to continue.
|
|
valgrindProcesses () {
|
|
if [ $CUSTOM_LOGPARAM = "no" ]; then
|
|
for file in $LOGFILES; do
|
|
pid=`echo $file | perl -p -e 's/.*valgrind\.p\d+\.c(\d+).*/$1/'`
|
|
echo $pid
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Any child valgrind processes still running?
|
|
valgrindRunning () {
|
|
for pid in `valgrindProcesses`; do
|
|
if ps $pid >/dev/null; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Kill child valgrind process with given list of signals.
|
|
killvalgrind () {
|
|
signals=$1
|
|
for pid in `valgrindProcesses`; do
|
|
if ps --no-headers ww $pid; then
|
|
for signal in $signals; do
|
|
echo "valgrind.sh: killing forked process $pid with signal $signal"
|
|
LC_ALL=C kill -$signal $pid 2>&1 | grep -v 'No such process'
|
|
done
|
|
fi
|
|
done
|
|
}
|
|
|
|
( set +x; echo "*** starting $1 under valgrind, output to ${VALGRIND_CMD_LOG:-stdout}" )
|
|
( set -x; if [ "$VALGRIND_CMD_LOG" ]; then exec >>"$VALGRIND_CMD_LOG" 2>&1; fi; exec env GLIBCXX_FORCE_NEW=1 G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind $VALGRIND_ARGS --gen-suppressions=all --log-file=$LOGPARAM "$@" $OUTPUT 2>&1 ) &
|
|
VALGRIND_PID=$!
|
|
|
|
intvalgrind () {
|
|
kill -INT $VALGRIND_PID
|
|
}
|
|
termvalgrind () {
|
|
kill -TERM $VALGRIND_PID
|
|
}
|
|
|
|
trap "kill -TERM $VALGRIND_PID" TERM
|
|
trap "kill -INT $VALGRIND_PID" INT
|
|
|
|
wait $VALGRIND_PID
|
|
RET=$?
|
|
echo "valgrindcheck ($$): '$@' ($VALGRIND_PID): returned $RET" >&2
|
|
|
|
# give other valgrind instances some time to settle down, then kill them
|
|
sleep 1
|
|
killvalgrind INT TERM
|
|
# let valgrind chew on leak checking for up to 30 seconds before killing it
|
|
# for good
|
|
i=0
|
|
while valgrindRunning && [ $i -lt 30 ]; do
|
|
sleep 1
|
|
i=`expr $i + 1`
|
|
done
|
|
killvalgrind KILL
|
|
# Filter out leaks in forked processes if VALGRIND_LEAK_CHECK_ONLY_FIRST is set,
|
|
# detect if unfiltered errors were reported by valgrind. Unfiltered errors
|
|
# are detected because valgrind will produce a suppression for us, which can
|
|
# found easily by looking for "insert_a_suppression_name_here".
|
|
#
|
|
# Here is some example output:
|
|
#
|
|
# ==13044== Memcheck, a memory error detector
|
|
# ==13044== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
|
|
# ==13044== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
|
|
# ==13044== Command: /tmp/test-fork
|
|
# ==13044== Parent PID: 13043
|
|
# ==13044==
|
|
# ==13044== Conditional jump or move depends on uninitialised value(s)
|
|
# ==13044== at 0x400555: main (test-fork.c:9)
|
|
# ==13044==
|
|
# {
|
|
# <insert_a_suppression_name_here>
|
|
# Memcheck:Cond
|
|
# fun:main
|
|
# }
|
|
# ==13044==
|
|
# ==13044== HEAP SUMMARY:
|
|
# ==13044== in use at exit: 0 bytes in 0 blocks
|
|
# ==13044== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
|
|
# ==13044==
|
|
# ==13044== All heap blocks were freed -- no leaks are possible
|
|
# ==13044==
|
|
# ==13044== For counts of detected and suppressed errors, rerun with: -v
|
|
# ==13044== Use --track-origins=yes to see where uninitialised values come from
|
|
# ==13044== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
|
|
# ==13047==
|
|
# ==13047== HEAP SUMMARY:
|
|
# ==13047== in use at exit: 100 bytes in 1 blocks
|
|
# ==13047== total heap usage: 1 allocs, 0 frees, 100 bytes allocated
|
|
# ==13047==
|
|
# ==13047== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
|
|
# ==13047== at 0x4C244E8: malloc (vg_replace_malloc.c:236)
|
|
# ==13047== by 0x40056E: main (test-fork.c:15)
|
|
# ==13047==
|
|
# {
|
|
# <insert_a_suppression_name_here>
|
|
# Memcheck:Leak
|
|
# fun:malloc
|
|
# fun:main
|
|
# }
|
|
# ==13047== LEAK SUMMARY:
|
|
# ==13047== definitely lost: 100 bytes in 1 blocks
|
|
# ==13047== indirectly lost: 0 bytes in 0 blocks
|
|
# ==13047== possibly lost: 0 bytes in 0 blocks
|
|
# ==13047== still reachable: 0 bytes in 0 blocks
|
|
# ==13047== suppressed: 0 bytes in 0 blocks
|
|
# ==13047==
|
|
# ==13047== For counts of detected and suppressed errors, rerun with: -v
|
|
# ==13047== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
|
|
#
|
|
# This output is meant to be printed completely when VALGRIND_LEAK_CHECK_ONLY_FIRST
|
|
# is not set. Otherwise the "definitely lost" error including its suppression
|
|
# shall be filtered out.
|
|
#
|
|
# 100 is returned by Perl either way because of the Cond error in the main process.
|
|
#
|
|
|
|
SUBRET=0
|
|
for i in $LOGFILES; do
|
|
perl \
|
|
-e '$onlyfirst = $ENV{VALGRIND_LEAK_CHECK_ONLY_FIRST};' \
|
|
-e '@leakskip = split(/,/, $ENV{VALGRIND_LEAK_CHECK_SKIP});' \
|
|
-e '%skippids = {};' \
|
|
-e '$ret = 0;' \
|
|
-e '$pid = 0;' \
|
|
-e '$skipping = 0;' \
|
|
-e 'while (<>) {' \
|
|
-e ' if (/^==(\d*)== Command: (.*)/) {' \
|
|
-e ' $newpid = $1; $command = $2;' \
|
|
-e ' foreach $pattern (@leakskip) {' \
|
|
-e ' if ($command =~ /$pattern/) {' \
|
|
-e ' $skippids{$newpid} = 1;' \
|
|
-e ' last;' \
|
|
-e ' }' \
|
|
-e ' }' \
|
|
-e ' } elsif (/^==(\d*)==/) {'\
|
|
-e ' $newpid = $1;' \
|
|
-e ' } else {' \
|
|
-e ' $newpid = 0;' \
|
|
-e ' }' \
|
|
-e ' if ($newpid && ($skipping == 1 && $pid == $newpid || $skipping == 2 && !$skippids{$newpid})) {' \
|
|
-e ' $skipping = 0;' \
|
|
-e ' }' \
|
|
-e ' if (!$skipping) {' \
|
|
-e ' $pid = $newpid unless $pid;' \
|
|
-e ' $ispotential = /(possibly|indirectly) lost in loss record/;' \
|
|
-e ' if ($newpid && $skippids{$newpid} && $ispotential) {' \
|
|
-e ' $skipping = 2;' \
|
|
-e ' } elsif ($newpid && $onlyfirst && $pid != $newpid && $ispotential) {' \
|
|
-e ' $skipping = 1;' \
|
|
-e ' } else {' \
|
|
-e ' print;' \
|
|
-e ' if (/insert_a_suppression_name_here/) {' \
|
|
-e ' $ret = 100;' \
|
|
-e ' }' \
|
|
-e ' }' \
|
|
-e ' }' \
|
|
-e '}' \
|
|
-e 'exit $ret;' \
|
|
$i
|
|
SUBSUBRET=$?
|
|
if [ $SUBRET -eq 0 ]; then
|
|
SUBRET=$SUBSUBRET
|
|
fi
|
|
done
|
|
|
|
# bad valgrind log result always overrides normal completion status:
|
|
# that way the valgrind errors also show up in the nightly test summary
|
|
# in the case where some other test already failed
|
|
if [ $SUBRET -ne 0 ]; then
|
|
RET=$SUBRET
|
|
echo valgrindcheck: "$@": log analysis overrides return code $RET with $SUBRET >&2
|
|
fi
|
|
rm $LOGFILES
|
|
|
|
echo valgrindcheck: "$@": final result $RET >&2
|
|
exit $RET
|