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 killer.pm
:
package killer;
$SIG{__WARN__} = sub { warn shift; exit 1 };
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 ]
then
eval_gettextln "bisect run failed:
exit code \$res from '\$command' is < 0 or >= 128" >&2
exit $res
fi
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 ;)
The comments to modify all files are interesting, but they don't handle the case where a git-bisect doesn't find those in previous commits.