2010-08-23

Forcing PHPUnit tests on Subversion commit

All of my PHP projects use PHPUnit for unit testing. One of my coworkers just refuses to understand the point of testing. He doesn't get Test Driven Development (TDD), and rarely runs the unit tests for his classes. This means that he frequently breaks my build. I can't force him to change his behavior since he doesn't work for me, but I can change the way my systems work. And I control the Subversion server.
I wanted to force unit tests to pass before allowing submission, but couldn't find anything about running PHPUnit tests in a Subversion pre-commit hook. So I wrote my own. Hopefully this will help someone out there:
#!/usr/local/bin/php
<?php
/**
* Pre-commit Subversion script that runs unit tests.
* @author Omni Adams <omni@digitaldarkness.com>
* @copyright 2010 Digital Darkness
*/
/**
* Path to the ant binary.
*/
define('ANT', '/usr/bin/ant');
/**
* Path to the patch binary.
*/
define('PATCH', '/usr/bin/patch');
/**
* Path to the php binary.
*/
define('PHP', '/usr/local/bin/php');
/**
* Path to the svn binary.
*/
define('SVN', '/usr/bin/svn');
/**
* Path to svnlook binary.
*/
define('SVNLOOK', '/usr/bin/svnlook');
/**
* Divider to inject into error messages.
*/
define('DIVIDER',
'********************************************************************************');
/**
* Run unit tests for the repository.
*
* If any unit tests fail, write the failure information
* to standard error and return true.
* @param string $transaction SVN transaction ID.
* @param string $repository Full path to the repository.
* @return boolen True if there was an error.
*/
function runUnitTests($transaction, $repository) {
// Check out the latest from the repository into a
// temporary directory.
$fullRepoUrl = 'file:///' . $repository . '/trunk';
$workingDirectory = '/tmp/svn-precommit/'
. $transaction;
mkdir($workingDirectory, 0700, true);
exec(SVN . ' co "' . $fullRepoUrl . '" '
. $workingDirectory);
// Pull a patch of files changed from the transaction.
$diffCommand = SVNLOOK . ' diff -t "' . $transaction
. '" "' . $repository . '"';
$diffOutput = array();
exec($diffCommand, $diffOutput);
$diff = implode(PHP_EOL, $diffOutput);
file_put_contents($workingDirectory . '/patch',
$diff);
// Apply the patch to a fresh copy of the repository.
chdir($workingDirectory);
exec(PATCH . ' < ' . $workingDirectory . '/patch');
// Run the test target.
$output = array();
exec(ANT . ' test 2> /dev/null', $output);
$output = implode(PHP_EOL, $output);
if (false === strpos($output, 'FAILURES')) {
// Build passed!
return false;
}
// Clean up the output a little bit before displaying
// it to the user.
$output = str_replace(array(' [exec]',
$workingDirectory), '', $output);
$output = PHP_EOL . DIVIDER . PHP_EOL . PHP_EOL
. 'Unit tests failed. Fix the unit tests before '
. 'submitting.' . PHP_EOL . PHP_EOL . $output;
file_put_contents('php://stderr', $output);
return true;
}
$repository = $_SERVER['argv'][1];
$transaction = $_SERVER['argv'][2];
if (runUnitTests($transaction, $repository)) {
exit(1);
}
exit(0);
To use this, copy it to the hooks directory in your Subversion repository and name it 'pre-commit'. Make sure the paths defined in the script are correct for your system. Make it executable (chmod +x pre-commit).

No comments:

Post a Comment