Simple continuous integration with PHP and subversion (or any other version control system)

2 minutters læsning

I work on a project in a subversion repository. Because knowing is half the battle, I wanted some kind of continuous integration on the project. Earlier I explained how you can use cijoe with github projects, but that was not an option. I wanted something simple, and came up with the following solution.

I already had a development server set up, and the test suite was written in PHPUnit. I wanted the test suite to be run regularly and notify me, if the tests did not pass. I solved it by a cron job updating the subversion repository and running the test suite, and a test listener informing me about the result.

The test listener

First I wrote the test listener. I basically just count whether there is any errors and failures. If there is, it notifies me via notifo.com, but you could also send an e-mail or anything similar.

<?php
class TestListener implements PHPUnit_Framework_TestListener
{
    protected $errors;
    protected $error_count;

    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->error_count++;
    }

    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        $this->error_count++;
    }

    public function addIncompleteTest(PHPUnit_Framework_Test $test,Exception $e,$time)
    {
    }

    public function addSkippedTest(PHPUnit_Framework_Test $test,Exception $e,$time)
    {
    }

    public function startTest(PHPUnit_Framework_Test $test)
    {
    }

    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
    }

    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    public function __destruct()
    {
        exec('svn info ' . dirname(__FILE__) . '/../', $output);
        $msg = 'Build had ' . $this->error_count . ' errors. Please fix them' . "\n\n";
        $msg .= implode("\n", $output);
        if ($this->error_count > 0){
            $this->notifo('project', 'Build failed', $msg, 'http://example.org', 'user', 'api_secret');
        }
    }

    function notifo($label, $title, $msg, $uri, $user, $pass)
    {
        $opts = array(
            'http' => array(
                'method' => "POST",
                'header' => "Authorization: Basic ".base64_encode($user.":".$pass)."\r\n".
                    "Content-type: application/x-www-form-urlencoded\r\n",
                'content' => http_build_query(array('label' => $label, 'title' => $title, 'msg' => $msg, 'uri' => $uri))
            )
        );
        $context = stream_context_create($opts);
        file_get_contents('https://api.notifo.com/v1/send_notification', false, $context);
    }
}

To attach the listener to your test suite, I used the following XML-configuration:

<phpunit colors="true">
  <listeners>
    <listener class="TestListener" file="TestListener.php"></listener>
  </listeners>
</phpunit>

Now I am able to run the test suite from the command line with the following command:

$ phpunit .

Putting it all together

I wrote a bash script to update the repository and run the test suite. It is really simple:

UPDATE=$(git pull origin master)
if ; then
    cd /home/intraface_test/intraface-git/tests
    phpunit --process-isolation --log-tap /home/intraface_test/test.intraface.dk/testresult . > /dev/null
    cd /home/intraface_test
fi

The script checks whether the repository has been updated. If it has, a local update is performed and the test suite is run. Now to make it continuous integration I just need to create a cronjob running the script on a regular basis.

*/15 * * * * sh myscript.sh

Improvements

  • Make sure that new builds are run in order and does not interfere with previous builds.

Could it be more simple? What do you think of this approach?