Fixing bugs is often a long and frustrating process. Especially if a bug sneaked into the code base some time ago, it’s not really obvious and has only now been discovered by accident. Sometimes developers will just fix a bug and be done with it. But we all know it’s better to understand why the bug was introduced and when (and sometimes, by whom!). This can allow us to understand the problem better and fix it in a way that won’t re-introduce the issue the original developer was trying to fix.
Depending on which version control system you are using, this experience can range from hair-pulling frustration to a smug grin on your face.
So let’s see how to get that grin. You will need two things: Git and BladeRunnerJS. Git, so we can use the mind-bogglingly-awesome git bisect run. And BladeRunnerJS, so we have a great testing environment.
Git bisect works by doing a binary-search between a good and a bad commit. After each step we verify if the bug is there and tell Git what we find. Eventually Git will present us with the commit that introduced the bug. As awesome it is, we developers are lazy creatures. Wouldn’t it be great to just let Git figure out, automatically, when a commit is bad, so we can spend our time doing better things? As it happens, there is. Git bisect comes with a sub-command called ‘run’ which accepts a script (any executable, really) that it runs for every commit that it bisects. If the script returns 0 as it’s exit code, Git will know that the commit is good, otherwise it’s a bad commit.
This means we can automate our bug finding adventure. All we need to do is write a failing test for the bug, a small shell script (I was testing this on OSX) and that’s it.
I’ve created a simple BRJS app, that has a MyUtils#isStringEmpty function that at some point started treating strings with just spaces as non-empty. To find out which commit introduced the bug I wrote this simple test:
(function() { var MyUtilsTestBugHunt = TestCase( 'MyUtilsTestBugHunt' ); var MyUtils = require('gbp/mybladeset/myblade/MyUtils'); MyUtilsTestBugHunt.prototype['test isStringEmpty works'] = function() { assertTrue(MyUtils.isStringEmpty(' ')); // this tests the bug that we are looking }; }());
I then saved the file next to the existing test for the MyUtils class, added it to git and stashed it (the script will un-stash it before each test run, and remove it after each test, to avoid any possible conflicts). Then I created a simple shell script that does the testing for me:
#!/bin/sh startPwd=${PWD} # add the test that finds the bug back into repository git stash apply stash@{0} > /dev/null 2>&1 # run the tests with BRJS cd ../../sdk ./brjs test ../apps/GitBisectApp/mybladeset-bladeset UTs > /dev/null 2>&1 testExitCode=$? cd $startPwd # remove the bug finding test file to avoid any conflicts while git bisect runs git reset HEAD mybladeset-bladeset/blades/myblade/tests/test-unit/js-test-driver/tests/MyUtilsBugHunt.js > /dev/null 2>&1 rm mybladeset-bladeset/blades/myblade/tests/test-unit/js-test-driver/tests/MyUtilsBugHunt.js # return the exit code from the brjs test command exit $testExitCode
A note on paths: my Git repository was the app inside ‘brjs/apps/GitBisectApp’, I’ve put the shell script into the ‘brjs’ folder. All the commands bellow are executed from the root of my repository (my app root).
I then told Git the bad and the good commits (as a good commit, I took the commit that added the first tests for the MyUtils class):
git bisect start HEAD 4840403475cccfb6c80886fc39d76455c21e2dc2
And ran the bisect run:
git bisect run ../../find_bug.sh
Which produced the faulty commit:
178e43390a97f98b4d63b1649e71dd483e1fca7d is the first bad commit commit 178e43390a97f98b4d63b1649e71dd483e1fca7d Author: Jan Hancic <jan.hancic@example.com> Date: Wed Jul 23 22:39:50 2014 +0100 refactor some code :040000 040000 05827c1d669fc89d395e2489310220ff43632b12 06518f4a2839ca1e6a0e3ff0ce7c949e6e5cdab7 Mmybladeset-bladeset bisect run success
Done! How cool was that?