Update: Hudson for C++/CMake/CppUnit Revised
As a follow-up to Using grails projects in Hudson, here is another not-so-standard usage of Hudson: C++ projects with CMake and CppUnit. Let’s see how that works out.
As long as you have Java/Ant/JUnit based projects, a fine tool that it is, configuration of Hudson is pretty straight forward. But if you have a C++ project with CMake as build system and CppUnit for your unit testing, you have to dig a little deeper. Fortunately, Hudson provides the possibility to execute arbitrary shell commands. So in order to build the project and execute the tests, we can simply put a shell script to work:
# define build and installation directories
BUILD_DIR=$WORKSPACE/build_dir
INSTALL_DIR=$WORKSPACE/install_dir
# we want to have a clean build
rm -Rf $BUILD_DIR
mkdir $BUILD_DIR
cd $BUILD_DIR
# initializing the build system
cmake .. -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR
# fire-up the compiler
make install
Environment variable WORKSPACE is defined by Hudson. Other useful variables are e.g. BUILD_NUMBER, BUILD_TAG and CVS_BRANCH.
But what about those unit tests? Hudson understands JUnit test result files out-of-the-box. So all we have to do is make CppUnit spit out an xml report and then translate it to JUnit form. To help us with that, we need a little xslt transformation. But first, let’s see how we can make CppUnit generate xml results (a little simplified):
#include <cppunit/necessary/CppUnitIncludes/>
...
using namespace std;
using namespace CppUnit;
int main(int argc, char** argv)
{
TestResult controller;
TestResultCollector result;
controller.addListener(&result);
CppUnit::TextUi::TestRunner runner;
runner.addTest( TestFactoryRegistry::getRegistry().makeTest() );
runner.run(controller);
// important stuff happens next
ofstream xmlFileOut("cpptestresults.xml");
XmlOutputter xmlOut(&result, xmlFileOut);
xmlOut.write();
}
The assumption here is that your unit tests are built into libraries that are linked with the main function above. To execute the unit tests we add the following to out shell script:
export PATH=$INSTALL_DIR/bin:$PATH
export LD_LIBRARY_PATH=$INSTALL_DIR/lib:$LD_LIBRARY_PATH
# call the cppunit executable
cd $WORKSPACE
cppunittests
This results in CppUnit generating file $WORKSPACE/cpptestresults.xml. Now, with the help of a little program called xsltproc and the following little piece of XSLT code, we can translate cpptestresults.xml to testresults.xml in JUnit format.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<testsuite>
<xsl:attribute name="errors"><xsl:value-of select="TestRun/Statistics/Errors"/></xsl:attribute>
<xsl:attribute name="failures">
<xsl:value-of select="TestRun/Statistics/Failures"/>
</xsl:attribute>
<xsl:attribute name="tests">
<xsl:value-of select="TestRun/Statistics/Tests"/>
</xsl:attribute>
<xsl:attribute name="name">from cppunit</xsl:attribute>
<xsl:apply-templates/>
</testsuite>
</xsl:template>
<xsl:template match="/TestRun/SuccessfulTests/Test">
<testcase>
<xsl:attribute name="classname" ><xsl:value-of select="substring-before(Name, '::')"/></xsl:attribute>
<xsl:attribute name="name"><xsl:value-of select="substring-after(Name, '::')"/></xsl:attribute>
</testcase>
</xsl:template>
<xsl:template match="/TestRun/FailedTests/FailedTest">
<testcase>
<xsl:attribute name="classname" ><xsl:value-of select="substring-before(Name, '::')"/></xsl:attribute>
<xsl:attribute name="name"><xsl:value-of select="substring-after(Name, '::')"/></xsl:attribute>
<error>
<xsl:attribute name="message">
<xsl:value-of select=" normalize-space(Message)"/>
</xsl:attribute>
<xsl:attribute name="type">
<xsl:value-of select="FailureType"/>
</xsl:attribute>
<xsl:value-of select="Message"/>
File:<xsl:value-of select="Location/File"/>
Line:<xsl:value-of select="Location/Line"/>
</error>
</testcase>
</xsl:template>
<xsl:template match="text()|@*"/>
</xsl:stylesheet>
The following call goes into our shell script:
xsltproc cppunit2junit.xsl $WORKSPACE/cpptestresults.xml > $WORKSPACE/testresults.xml
In the configuration page we can now check “Display JUnit test results” and give testresults.xml as result file. As a last step, we can package everything in $WORKSPACE/install_dir into a .tgz file and have Hudson to store it as build artifact. That’s it!
As always, there is room for improvements. One would be to wrap the shell script code above in a separate bash script and have Hudson simply call that script. The only advantage of the approach above is that you can see what’s going on directly on the configuration page. If your project is bigger, you might have more than one CppUnit executable. In this case, you can for example generate all testresult.xml files into a separate directory and tell Hudson to take into account all .xml files there.
Update: For the CMake related part of the above shell script I recently published the first version of a cmakebuilder plugin for Hudson. Check out my corresponding blog post.