Wednesday 14 October 2009

Running unit tests under gdb: Part II

In part one I described a script which could be used to easily run unit tests under gdb. In describing the script I wrote:

It's not ideal — it corrupts quoting of its arguments and arbitrarily limits the number of arguments but it does work.

I am indebted to a loyal reader for pointing out the rather obvious --args gdb parameter. I'm not really sure how I'd missed it. This makes fixing the script to remove those flaws trivial. If you're running only one test that you know is going to fail then it may be sufficient to just use RUN_TEST="gdb --args". However, if the problem is intermittent or you're running a whole gaggle of tests then the fixed script is still useful.

This version of the script also ensures that the temporary commands file is cleaned up too.

#!/bin/sh
cmds=`tempfile -p rug`
: > $cmds
echo 'run' >> $cmds
echo 'if $_exitcode == 0' >> $cmds
echo ' quit' >> $cmds
echo 'end' >> $cmds
trap 'if [ -n "$cmds" ]; then rm -f "$cmds" ;exit 1; fi' SIGINT SIGHUP SIGTERM ERR

if gdb --return-child-result --quiet -x $cmds --args "$@"; then
rm -f $cmds
else
result=$?
rm -f $cmds
exit $result
fi

Tuesday 13 October 2009

Running unit tests under gdb: Part I

Our build system usually runs all the unit tests under valgrind. It does this by preceding the invocation of the compiled unit test with $(RUN_TEST) as in this simplified version:
RUN_TEST?=valgrind
my_unittest.pass : my_unittest
$(RUN_TEST) my_unittest
touch $@
This means that it's easy to run the test without valgrind by invoking
make RUN_TEST= my_unittest.pass
This can be useful when you know the test is going to fail and you don't want the huge amount of output that valgrind will spew when you abort() from the depths.

Sometimes it would be handy to run the unit tests under gdb but unfortunately invoking a program under gdb is not the same as invoking it normally so RUN_TEST=gdb doesn't work. Compare:
valgrind myprog myargs...
with:
gdb myprog
run myargs...
So, what's needed is a gdb wrapper script that turns the former method of invocation into the latter. It turns out that it's possible to do even better than that. The following script runs the program under gdb and if it succeeds just exits normally so that the next test can be tried. If the program breaks due to an abort then the if statement unfortunately produces an error message but it does have the intended effect — you're left at the gdb prompt.

Here's my run-under-gdb script:
#!/bin/sh
cmds=`tempfile -p rug`
prog=$1
shift
: > $cmds
echo "run $1 $2 $3 $4 $5 $6 $7 $8 $9" >> $cmds
echo 'if $_exitcode == 0' >> $cmds
echo ' quit' >> $cmds
echo 'end' >> $cmds
if gdb -x $cmds $prog; then
rm -f $cmds
else
result=$?
rm -f $cmds
exit $result
fi

It's not ideal — it corrupts quoting of its arguments and arbitrarily limits the number of arguments but it does work. Part two contains an improved script that solves these problems and provides other fixes.

Monday 5 October 2009

A better way to view man pages in a different window

In Episode 13 of Season 2 of the Ubuntu UK Podcast the “Command Line Lurve” segment was supplied by Alan Bell. He found that reading man pages in a terminal to be painful because the terminal was where he wanted to being typing the command options. His script ran gnome-help in the background to view the man page in a different window.

Alan Bell's script prompted me to write an improved version and send it in along with this email:

I listened to Alan Bell's command line luurve segment in your podcast
this evening. Whilst his script works it inhibits much of man(1)'s
functionality. In particular it does not support specifying the manual
section (compare "man open" and "man 2 open" for example.)

Here's my alternative that maintains this functionality and
automatically falls back to standard man if any options are supplied:

#!/bin/sh
x="$DISPLAY"
case "$1" in
"") x= ;;
-*) x= ;;
esac
if [ -n "$x" ]; then
section=
for i in "$@"; do
case "$i" in
[0-9]) section="($i)" ;;
*) gnome-help "man:$i$section" >/dev/null 2>&1 & ;;
esac
done
else
exec man "$@"
fi

The script also makes specifying multiple pages at once more useful
than it is with man(1).
It can be aliased to man if required as Alan described.



They were nice enough to read out my email in Episode 14 but the script didn't appear in the show notes. So here it is.