Using git-bisect to track down warnings in your test suite

You've all seen this before:

$ prove -l t/some/test.t
t/some/test.t .. 51/? Use of uninitialized value $foobar in ...
t/some/test.t .. ok

Inspecting the code doesn't necessarily make it clear what the correct fix is, so you want to kick it back to the person who generated the warning. But that's not actually a test failure, so git-bisect doesn't work out of the box.

If you're not familiar with git-bisect, it works like this:

git bisect start
git checkout known-bad-commit
git bisect bad
git checkout known-good-commit
git bisect good

At this point, git inspects the commits between your good and bad commits and then presents you with something like this:

Bisecting: 86 revisions left to test after this (roughly 7 steps)
[f7d4ca1acca7c215b7bb1809e93e2923bdbb068e] Bad commit message here ...
03:05:34 {(f7d4ca1...)|BISECTING} ~/ $

What it's doing is picking a commit roughly halfway between your two commits. You then test that commit and type either git bisect bad or git bisect good depending on on whether the commit was bad or good. git-bisect then basically does a binary search through your commits to find the first bad commit.

However, I find that tedious. I prefer git bisect run COMMAND. If COMMAND exits with a value between 0 and 127 (inclusive), git will use the normal exit status (0 for good, otherwise bad) to do the binary search for you. In other words, I often do this:

git bisect run prove -l t/some/test.t

And it takes me straight to the offending commit.

But warnings aren't failures, as I mentioned. So I tried something different. I wrote a tiny module called

package killer;
$SIG{__WARN__} = sub { warn shift; exit 1 };

Nothing fancy: I just want fatal warnings and use warnings FATAL => 'all' is lexically scoped. I also put the exit 1 in there because I really don't care about the exit value, but in some cases, Perl tests will exit with a value greater than 127 and git-bisect has this fun code in it:

# Check for really bad run error.
if [ $res -lt 0 -o $res -ge 128 ]
    eval_gettextln "bisect run failed:
exit code \$res from '\$command' is < 0 or >= 128" >&2
    exit $res

Meaning that we won't get a failure if our failure code is out of range, so exit 1 is just fine.

Then you can do this:

git bisect run perl -Ilib -Mkiller t/some/test.t

And git-bisect will happily tell you the first commit that threw the warnings. I'm sure there are better ways to do this, so tell me about 'em (simpler ways I tried wound up throwing exit codes of 255, making everything fall down and go boom).


I simply add $SIG{WARN}=sub{die} to all my tests permanently.

It would be a failure if you added 'use Test::Warnings;' to all your .t files ;)

Leave a comment

About Ovid

user-pic Have Perl; Will Travel. Freelance Perl/Testing/Agile consultant. Photo by Warning: that site is not safe for work. The photographer is a good friend of mine, though, and it's appropriate to credit his work.