måndag 8 december 2008

Scons and boost test

A few days a ago I decided I wanted to use boosts unit testing framework with scons. So I googled for some help on how to integrate boost.test with scons. This resulted in finding a wikipage at the scons website.

Some code for boost test was found under the title "Unit Test integration with an scons Tool". But that page didn't explain much, it threw some code at me and left it up to me to figure out where to put it. It took me about a day to solve all the issues, so now that I know how to do it I thought I'd share a more complete guide.

Note that this is just a guide on how to get it working. I haven't even explored any nifty modifications myself yet.

Step 1: In the main Sconstruct file
I assume you have an environment made (called env), the following code is added after that.
This script initiates a tool that compiles and runs your test cases.


### Testing ###
Export('env')
# Set up the test environment. We copy the environment so that we can add the
# extra libraries needed without messing up the environment for production
# builds.
#
# Here we use boost.test as the unit testing framework.
testEnv = env.Clone()
testEnv.Tool('unittest',
toolpath=['build_tools'],
UTEST_MAIN_SRC=File('build_tools/boostautotestmain.cpp'),
LIBS=['boost_unit_test_framework']
)
Export('testEnv')
# grab stuff from sub-directories.
env.SConscript(dirs = ['test'])


Step 2: The tests main function
Make a build_tools folder in your project folder.
Create the file boostautotestmain.cpp in that folder and put the following code in it.
Yes, this is the toolpath and UTEST_MAIN_SRC from step one, so you can adapt this if you don't like the naming.
This code is for creating the main function for your tests. I'm using the auto test cases.
If you want to run tests differently please read the boost docs or google for help.


#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MAIN
#include


Step 3: The tool script
In your project folder add the folder site_scons and under that the folder site_tools.
Then put the following code in unittest.py in there, so it's theproject/site_scons/site_tools/unittest.py.
That's not my idea, it's scons standard for adding local tools. If you want to make the tool global, please read scons docs.


import os
def unitTestAction(target, source, env):
'''
Action for a 'UnitTest' builder object.
Runs the supplied executable, reporting failure to scons via the test exit
status.
When the test succeeds, the file target.passed is created to indicate that
the test was successful and doesn't need running again unless dependencies
change.
'''
app = str(source[0].abspath)
if os.spawnle(os.P_WAIT, app, env['ENV'])==0:
open(str(target[0]),'w').write("PASSED\n")
else:
return 1
def unitTestActionString(target, source, env):
'''
Return output string which will be seen when running unit tests.
'''
return 'Running tests in ' + str(source[0])
def addUnitTest(env, target=None, source=None, *args, **kwargs):
'''
Add a unit test
Parameters:
target - If the target parameter is present, it is the name of the test
executable
source - list of source files to create the test executable.
any additional parameters are passed along directly to env.Program().
Returns:
The scons node for the unit test.
Any additional files listed in the env['UTEST_MAIN_SRC'] build variable are
also included in the source list.
All tests added with addUnitTest can be run with the test alias:
"scons test"
Any test can be run in isolation from other tests, using the name of the
test executable provided in the target parameter:
"scons target"
'''
if source is None:
source = target
target = None
source = [source, env['UTEST_MAIN_SRC']]
program = env.Program(target, source, *args, **kwargs)
utest = env.UnitTest(program)
# add alias to run all unit tests.
env.Alias('test', utest)
# make an alias to run the test in isolation from the rest of the tests.
env.Alias(str(program[0]), utest)
return utest
#-------------------------------------------------------------------------------
# Functions used to initialize the unit test tool.
def generate(env, UTEST_MAIN_SRC=[], LIBS=[]):
env['BUILDERS']['UnitTest'] = env.Builder(
action = env.Action(unitTestAction, unitTestActionString),
suffix='.passed')
env['UTEST_MAIN_SRC'] = UTEST_MAIN_SRC
env.AppendUnique(LIBS=LIBS)
# The following is a bit of a nasty hack to add a wrapper function for the
# UnitTest builder, see http://www.scons.org/wiki/WrapperFunctions
from SCons.Script.SConscript import SConsEnvironment
SConsEnvironment.addUnitTest = addUnitTest
def exists(env):
return 1


Step 4: Adding some tests
Make a file called SConscript in test folder, or whereever you have test sources.
The tool only processes tests that are in directories specified in the last line of the script in step 1.
And here's the code to put in SConscript.


#-------------------------------------------------------------------------------
# Unit tests
Import('testEnv')
Import('env')
testEnv = testEnv.Clone()
testEnv.AppendUnique(LIBPATH=[env.Dir('../lib')], LIBS=['rosal_renderer'])
testEnv.PrependENVPath('LD_LIBRARY_PATH', env.Dir('.').abspath)
# We can add single file unit tests very easily.
#testEnv.addUnitTest('test.cpp')
# also, multiple files can be compiled into a single test suite.
#files = Split('''
# one_test.cpp
# two_test.cpp
# ''')
#testEnv.addUnitTest('test_suite', files)
# you can also use glob to add all the files in the folder.
files = glob.glob('*.cpp')
testEnv.addUnitTest('test_suite', files)
# all the tests added above are automatically added to the 'test' alias.

2 kommentarer:

alind sharma sa...

Can you put en example project code directory including the scons-boost-unittest examples. So that I can execute it directly and see the magic work. You can also email me at alindsharma@gmail.com the whole ready to run directory which has some test cases. Thanks in advance.
ps : great work and a really nice post

Blake Miller sa...

Concise and helpful, TY very much :)