how to execute tests on a bazaar pre-commit hook

20 01 2008
you should always execute the test suite from a project before committing the source code to a version control system. everybody knows this but sometimes we forget… so lets have a pre-commit hook that will automatically execute the test suite. i am using bazaar.

in bazaar you write hooks as plugins.

how it works

the “hook” is simple python function that will be executed before really committing when bzr commit is called . it is very simple, it just run an executable and check its result. if it is successful the commit operation is executed. if it fails the commit operation is cancelled.

plugins in bazaar are not project specific. so you cant control in which projects (branches) your plugin will be applied (it will be applied to all). so, first the plugin checks if the branch contains a “precommit” executable. it must be named “precommit” and must be in root path of your branch.

the plugin file should be placed in ~/.bazaar/plugins/. the name of the module doesn’t matter (i called it “check_pre_commit.py”). putting the boilerplate code together i got this:

"""this is a plugin/hook for bazaar. just add this file to ~/.bazaar/plugins/"""

from bzrlib import branch

def pre_commit_hook(local, master, old_revno, old_revid, future_revno, future_revid, tree_delta, future_tree):
    """This hook will execute precommit script from root path of the bazaar
    branch. Commit will be canceled if precommit fails."""

    import os,subprocess
    from bzrlib import errors

    # this hook only makes sense if a precommit file exist.
    if not os.path.exists("precommit"):
        return
    try:
        subprocess.check_call(os.path.abspath("precommit"))
    # if precommit fails (process return not zero) cancel commit.
    except subprocess.CalledProcessError:
        raise errors.BzrError("pre commit check failed.")

branch.Branch.hooks.install_hook('pre_commit', pre_commit_hook)
branch.Branch.hooks.name_hook(pre_commit_hook, 'Check pre_commit hook')

thats all for the hook.

now we just need to create an executable that will run the tests and return something different from zero if it fails. and put it on the root path of the branch.
in this example i execute my tests using nose.

precommit

#!/bin/sh
nosetests

ok. everything is working. but who remember to run the tests should be “punished” and will have to wait for the tests to run twice (once manually before committing and once automatically as a pre-commit hook)? no. if you remember to run the tests you can also remember to skip the plugin :)

bzr commit --no-plugins

Update: You can put your tests in a smart build-system (like doit) to keep track of the modifications in your source code and re-execute only the required tests (the ones that depends on the modified files).

caveats

  • the precommit executable is part of the branch but the hook is not. so just doing a branch on the project is not enough. every user has to install the plugin. but just once for all projects.

  • the hook is executed after you supply the commit message. it is annoying to write a message and than loose it because the commit failed. or maybe it is a good punishment for trying to commit an untested code :)

  • the output gets a bit messed up. but not a big deal…

    eduardo@eduardo-laptop:~/work/doit$ bzr commit
    Committing to: /home/eduardo/work/doit/
    modified precommit
    [==========      ] Running pre_commit hooks [Check pre_commit hook] - Stage 3/5....
    ----------------------------------------------------------------------
    Ran 4 tests in 0.268s
    
    OK
    Committed revision 2.

thats it.


Actions

Information

2 responses

10 01 2011
Graham

The code is out of date, here it is updates for Bazaar 2.2.1, getting the base path of the branch:

from bzrlib import branch, urlutils

def pre_commit_script(local, master, old_revno, old_revid, future_revno, future_revid, tree_delta, future_tree):
“””This hook will execute precommit script from root path of the bazaar
branch. Commit will be canceled if precommit fails.”””
import os, subprocess
from bzrlib import errors
base = urlutils.local_path_from_url((local if local else master).base)
script = os.path.join(base, “precommit”)
# this hook only makes sense if a precommit file exist.
if not os.path.exists(script):
return
try:
subprocess.check_call(script)
# if precommit fails (process return not zero) cancel commit.
except subprocess.CalledProcessError:
raise errors.BzrError(“pre commit check failed.”)

branch.Branch.hooks.install_named_hook(
‘pre_commit’, pre_commit_script,
‘pre_commit_script (runs “precommit” script in branch root )’)

29 08 2011
jcollado

Hello Eduardo,

I was looking for some example to write a post_push hook to automate documentation build in readthedocs.org, but I certainly ended up writing one for running unit test cases before every commit also. Thanks!

Regards,
Javier

Leave a reply to jcollado Cancel reply