bullet3/mk/jam/unittest.jam
res2002 ec56a978f7 Regenerated configure & VC projects.
Misc property fixes.
2006-06-17 18:23:38 +00:00

577 lines
21 KiB
Plaintext

#==============================================================================
# Jam rules for unit testing with CppTest (http://cpptest.sourceforge.net/)
# Copyright (C) 2005 by Eric Sunshine <sunshine@sunshineco.com>
#
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Library General Public License as published by
# the Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
# License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#==============================================================================
if $(CPPUNIT.AVAILABLE) = yes
{
UNITTEST_VERBOSE ?= yes ;
UNITTEST_RUNFLAGS ?= ;
if $(UNITTEST_VERBOSE) = yes
{
UNITTEST_RUNFLAGS += "--verbose" ;
}
## UnitTest module [ : testdirs [ : extensions ] ]
##
## This rule provides a dynamic unit testing framework which utilizes CppUnit
## (http://cpptest.sourceforge.net/). The invocation:
##
## UnitTest <module> ;
##
## sets up unit testing support for `module' if $(SUBDIR)/t/ exists for
## `module' and contains test fragment files (*.t) and/or header (*.h) files.
## If `testdirs' is provided, then it is a list of directory to scan for test
## fragments instead of `t/'. If `extensions' is provided, then it is a list of
## file extensions for which to search instead of `.t'.
##
## It is safe to invoke this rule for modules which do not have a `t/'
## subdirectory, in which case the rule invocation is silently ignored. The
## UnitTest rule automatically synthesizes an appropriate driver program which
## incorporates the test fragment files (*.t), thus eliminating a boring and
## error-prone task.
##
## The Application, Library, and Plugin rules automatically invoke UnitTest on
## behalf of the client (unless given the `notest' option), so the vast
## majority of modules in a project inherit unit testing support automatically
## and for free. Simply populating a `t/' subdirectory with unit testing
## fragment files (*.t) is typically all that is needed to enable unit testing
## for an application, library, or plugin.
##
## The unit testing Jam target for `module' is named "check_module". In
## addition to this module-level granularity, the UnitTest rule also provides
## dynamic directory-based granularity. For example, presume that the following
## abbreviated list of directories exist in a project tree:
##
## apps/example
## libs/common
## plugins/bindings/perl_b
## plugins/bindings/python_b
## plugins/bindings/ruby_b
##
## The following module-based unit testing Jam targets will be created
## (assuming that the appropriate test fragment files, $(SUBDIR)/t/*.t, exist
## in each directory):
##
## check_example
## check_common
## check_perl_b
## check_python_b
## check_ruby_b
##
## Furthermore, the following directory-based targets also will be created
## dynamically:
##
## check (and its alias check_all)
## check_apps
## check_apps_example
## check_libs
## check_libs_common
## check_plugins
## check_plugins_bindings
## check_plugins_bindings_perl_b
## check_plugins_bindings_python_b
## check_plugins_bindings_ruby_b
##
## The very neat thing about the directory-based targets is that they
## automatically synthesize a driver program which incorporates all of the
## tests contained in child directories. For instance, the `check_plugins'
## target creates a unit testing driver which incorporates the tests from the
## bindings/perl_b/t, bindings/python_b/t, and bindings/ruby_b/t directories;
## and the `check_all' target creates a driver which incorporates all tests in
## the entire project. This allows entire selected branches of tests to be run
## all at once rather than having to run several different driver programs to
## get a complete report.
##
## The unit testing framework automatically wraps each test fragment file (*.t)
## into a synthesized C++ source file containing CppUnit boilerplate, such as
## necessary #include directives, #defines, etc. This allows the fragment
## files to be as simple as possible. They need #include only headers related
## to the actual module being tested, but need not worry about setting up the
## CppUnit environment since that is done automatically. A typical fragment
## file might look like this:
##
## /* Test file: libs/common/t/array.t */
##
## #include "common/myarray.h"
##
## class MyArrayTest : public CppUnit::TestFixture
## {
## public:
## void setUp() { ...set up test data... }
## void tearDown() { ...destroy test data... }
##
## void testInsert() { ...test array insertion methods... }
## void testDelete() { ...test array deletion methods... }
##
## CPPUNIT_TEST_SUITE(MyArrayTest);
## CPPUNIT_TEST(testInsert);
## CPPUNIT_TEST(testDelete);
## CPPUNIT_TEST_SUITE_END();
## };
##
## When the UnitTest rule synthesizes C++ wrappers for the test fragments, it
## needs to figure out the names of classes which contain tests. The synthesis
## process makes this determination by examining *.t and *.h files for
## subclasses of CppUnit::TestFixture. For this to work correctly, the
## "class Foo : public CppUnit::TestFixture" declaration must not be split over
## multiple lines.
##
## It is possible to have extra text added at the beginning and end of the
## synthesized C++ source code for both the *.t wrappers and the driver
## programs by optionally defining the following Jam variables (perhaps in
## Jamrules) with whatever text you would like inserted into the synthesized
## files:
##
## UNITTEST_BOILERPLATE_TEST_PRE
## UNITTEST_BOILERPLATE_TEST_POST
## UNITTEST_BOILERPLATE_MAIN_PRE
## UNITTEST_BOILERPLATE_MAIN_POST
##
## Thest `TEST' variables apply to the *.t wrappers, and the `MAIN' variables
## apply to the driver programs into which the wrappers are linked. If you find
## that you are including common code in all of your *.t files, then you may
## want to simplify by propagating the common code to the `TEST' boilerplate
## instead.
##
rule UnitTest
{
local mod = $(1) ;
local testdirs = $(2) ;
local exts = $(3) ;
if ! $(testdirs) { testdirs = t ; }
if ! $(exts) { exts = .t ; }
local test_clean = [ UnitTestNameClean $(mod) ] ;
local oldsubdir = $(SUBDIR_TOKENS) ;
local testdir ;
for testdir in $(testdirs)
{
SubDir TOP $(oldsubdir) $(testdir) ;
local tests_obj ;
local tests = [ Recurse : $(exts) ] ;
local headers = [ Recurse : .h .hpp .hxx .H ] ;
if $(tests) || $(headers)
{
tests = [ DoSourceGrist $(tests) ] ;
SEARCH on $(tests) = $(SEARCH_SOURCE) ;
local tests_src = $(tests:S=.cpp) ;
MakeLocate $(tests_src) : $(LOCATE_TARGET) ;
Clean $(test_clean) : $(tests_src) ;
# For each fragment (*.t), create a C++ wrapper. Automatically register
# subclasses of CppUnit::TestFixture.
local i ;
for i in $(tests)
{
Includes $(i:S=.cpp) : $(i) ;
UnitTestSource $(i:S=.cpp) : $(i) ;
}
# Also search for CppUnit::TestFixture subclasses in headers (*.h) and
# synthesize a C++ wrapper which registers them.
if $(headers)
{
headers = [ DoSourceGrist $(headers) ] ;
SEARCH on $(headers) = $(SEARCH_SOURCE) ;
local suitereg = [ DoSourceGrist __suitereg.cpp ] ;
MakeLocate $(suitereg) : $(LOCATE_TARGET) ;
UnitTestSource $(suitereg) : $(headers) ;
Includes $(suitereg) : $(headers) ;
tests_src += $(suitereg) ;
}
# Compile all synthesized sources.
tests_obj = [ CompileObjects $(tests_src) ] ;
CCHDRS on $(tests_obj) += [ FIncludes $(SEARCH_SOURCE) ] ;
C++FLAGS on $(tests_obj) += $(COMPILER.C++FLAGS.EXCEPTIONS.ENABLE)
$(CPPUNIT.CFLAGS) ;
Clean $(test_clean) : $(tests_obj) ;
# Create the driver for "check_module".
UnitTestDriver $(mod) : $(tests_obj) ;
# Dynamically create the drivers for all parent directories.
UnitTestDynamicTargets $(mod) : $(tests_obj) : $(oldsubdir) ;
}
}
SubDir TOP $(oldsubdir) ;
}
#------------------------------------------------------------------------------
# PRIVATE UTILITY RULES
#------------------------------------------------------------------------------
# UnitTestDriver module : objs [ : owner ]
# Given a set of object files which represent test fragment wrappers for
# `module', set up the driver program which incorporates them, and create the
# "check_module" and "check_moduleclean" targets. For invocations which arise
# from the UnitTest rule, `module' will be the actual module for which
# testing is being arranged, and `objs' will be the complete set of test
# objects for `module'. For directory-based testing drivers, `module' will be
# the dynamically synthesized module name representing the directory for
# which testing is being arranged, and `objs' will be only a subset of all
# objects which ultimately will be incorporated into this synthesized driver
# (specifically, the subset will be the objects belonging to `owner'). This
# rule may be invoked multiple times for the same synthesized directory-based
# driver `module' in order to accumulate the object files from all child
# directories (recursively).
#
# Since the driver programs in parent directories are actually conglomerates
# of the objects from many different modules, this rule is invoked multiple
# times for any given directory-based driver program. Each invocations
# presents it with a different set of object files. Therefore, it must take
# special care. In particular, the first time this rule is invoked for a
# synthesized driver in a particular directory, it actually creates the
# application target; on subsequent invocations for the same directory, it
# merely adds `objs' to the already-created application target. This way,
# the a directory's dynamically synthesized driver program can incorporate
# objects from all of its child directories (recursively).
#
# The `owner' is the module which owns the object files, `objs'. In the
# example illustrated for the UnitTest rule, when the "check_libs" driver
# program is under creation from within the `UnitTest common' invocation, the
# owner will be "common". This information is needed in order to ensure that
# the driver program synthesized at the "libs" level, which incorporates
# "common"'s object files, can gain access to "common's" linker flags (since
# they will be needed for linking the directory-based driver). If `owner' is
# not provided, then it defaults to `module'.
rule UnitTestDriver
{
local mod = $(1) ;
local objs = $(2) ;
local owner = $(3) ;
if ! $(owner) { owner = $(mod) ; }
local test_name = [ UnitTestNameTest $(mod) ] ;
local test_clean = [ UnitTestNameClean $(mod) ] ;
# This is the low-level target name by which a unit testing driver program is
# known. We only create the target the first time we are called at a
# particular directory level (thus the module_UNITTESTS check). Upon
# subsequent invocations, we merely add the new objects files to the existing
# driver.
local test_driver = $(mod)_unittest ;
if ! $($(mod)_UNITTESTS)
{
$(mod)_UNITTESTS = $(test_driver) ;
# Create the actual driver program represented by the `test_driver' target.
local test_driver_target =
[ DoObjectGrist [ ConstructApplicationTarget __unittest : console ] ] ;
MakeLocate $(test_driver_target) : $(LOCATE_TARGET) ;
$(test_driver)_TYPE = application ;
$(test_driver)_TARGET = $(test_driver_target) ;
$(test_driver)_OBJECTS = $(objs) ;
SystemLinkApplication $(test_driver) : $(objs) [ UnitTestCommonObj ] :
console ;
CFlags $(test_driver) : $(APPLICATION.CFLAGS) ;
LFlags $(test_driver) : $(LINKLIBS) $(APPLICATION.LFLAGS)
$(CPPUNIT.LFLAGS) ;
Depends $(test_name) : $(test_driver_target) ;
Clean $(test_clean) : $(test_driver_target) ;
# Actually run the unit tests.
NotFile $(test_name) $(test_clean) ;
Always $(test_name) $(test_clean) ;
Depends checkclean : $(test_clean) ;
UnitTestRun $(test_name) : $(test_driver_target) ;
}
else
{
ExtraObjects $(test_driver) : $(objs) ;
}
# Apply appropriate linker flags to the driver program. This has two parts:
# (1) If these are the unit tests for a library, then, as a convenience,
# assume that the tests need to link against that library.
# (2) For directory-based test targets, the driver needs all of the linker
# flags required by its child directories (recursively). For example, in
# the earlier cited illustration, the "check_all", and "check_libs"
# targets will also need whatever linker flags libs/common itself
# requires.
if $($(owner)_TYPE) = library { LinkWith $(test_driver) : $(owner) ; }
LinkWith $(test_driver) : [ on $($(owner)_TARGET) GetVar NEEDLIBS ] ;
return $(test_driver) ;
}
# UnitTestCommonObj
# Create object files common to all driver programs. Presently, the only
# common component is the main() function, which utilizes CppUnit's automatic
# test discovery protocol to discover test classes. (These are the subclasses
# of CppUnit::TestFixture for which we scan and pass to the
# CPPUNIT_TEST_SUITE_REGISTRATION() macro.)
rule UnitTestCommonObj
{
if ! $(UNITTEST_COMMON_OBJ)
{
local test_main_dir = [ ConcatDirs $(LOCATE.OBJECTS) __unittest_common ] ;
local test_main_src = main.cpp ;
test_main_src = $(test_main_src:G=__unittest) ;
MakeLocate $(test_main_src) : $(test_main_dir) ;
UnitTestMain $(test_main_src) ;
Clean checkclean : $(test_main_src) ;
local test_main_obj = [ CompileObjects $(test_main_src) ] ;
MakeLocate $(test_main_obj) : $(test_main_dir) ;
C++FLAGS on $(test_main_obj) += $(COMPILER.C++FLAGS.EXCEPTIONS.ENABLE)
$(CPPUNIT.CFLAGS) ;
Clean checkclean : $(test_main_obj) ;
UNITTEST_COMMON_OBJ = $(test_main_obj) ;
}
return $(UNITTEST_COMMON_OBJ) ;
}
# UnitTestDynamicTargets module : objs : subdir_tokens
# Given a set of subdirectory tokens representing the location of `module' in
# the source tree, dynamically synthesize a test driver program in each
# parent directory leading up to module's location. Each synthesized test
# program will incorporate module's `objs', as well as the objects of all
# other children (recursively) of the directory containing each driver. (The
# additional objects will be incorporated by subsequent invocations for the
# same directories.)
rule UnitTestDynamicTargets
{
local mod = $(1) ;
local objs = $(2) ;
local subdir_tokens = $(3) ;
# There is no need to synthesize a driver for the directory in which `module'
# itself resides, since we already have a "check_module" target for that.
# Therefore, simply alias this directory entry to the existing "check_module"
# target.
local deepest_name = [ UnitTestNameTest $(subdir_tokens:J=_) ] ;
local deepest_clean = [ UnitTestNameClean $(subdir_tokens:J=_) ] ;
NotFile $(deepest_name) $(deepest_clean) ;
Depends $(deepest_name) : [ UnitTestNameTest $(mod) ] ;
Depends $(deepest_clean) : [ UnitTestNameClean $(mod) ] ;
# For each parent directory of `module', synthesize a driver target.
local tokens = [ FReverse $(subdir_tokens) ] ;
tokens = $(tokens[2-]) ;
while $(tokens)
{
UnitTestDynamicTarget $(mod) : $(objs) : [ FReverse $(tokens) ] ;
tokens = $(tokens[2-]) ;
}
# Synthesize a "check_all" target which incorporates all tests projectwide.
UnitTestDynamicTarget $(mod) : $(objs) : : all ;
}
# UnitTestDynamicTarget module : objs : dir_tokens [ : dyn_module ]
# The workhorse for UnitTestDynamicTargets which actually changes to the
# specified directory and creates the driver program. The "check_foo" target
# name is normally composed of the directory tokens joined with underscores
# (i.e. "check_dir_tokens") unless the optional `dyn_module' is provided, in
# which case the target name becomes "check_dyn_module".
rule UnitTestDynamicTarget
{
local mod = $(1) ;
local objs = $(2) ;
local dir_tokens = $(3) ;
local dyn_mod = $(4) ;
if ! $(dyn_mod) { dyn_mod = $(dir_tokens:J=_) ; }
local olddir = $(SUBDIR_TOKENS) ;
SubDir TOP $(dir_tokens) ;
local test_driver = [ UnitTestDriver $(dyn_mod) : $(objs) : $(mod) ] ;
# Use module_UNITTESTS to remember that this dynamically synthesized driver
# has a relation to `module'. This information is needed later when clients
# invoke CFlags, LFlags, and LibDepends for `module'. Not only must those
# settings be applied to `module', but we must also apply them to module's
# test driver, as well as all of the directory-based drivers which
# incorporate module's test objects. This is what the UnitTestCFlags,
# UnitTestLFlags, and UnitTestLibDepends rules do.
$(mod)_UNITTESTS += $(test_driver) ;
SubDir TOP $(olddir) ;
}
# UnitTestNameTest module
# Return the name of the "check_module" target for `module'.
rule UnitTestNameTest
{ return check_$(<) ; }
# UnitTestNameClean module
# Return the name of the "check_moduleclean" target for `module'.
rule UnitTestNameClean
{ local n = [ UnitTestNameTest $(<) ] ; return $(n)clean ; }
# UnitTestCFlags module : flags [ : options ]
# Hook invoked automatically by CFlags. Applies `flags' also to module's test
# driver.
rule UnitTestCFlags
{
# Empty for now. Presumably the compiler flags are needed only by the actual
# sources of `module'; not by its tests which merely link against module's
# objects. This assumption may be wrong, and may change in the future.
}
# UnitTestLFlags module : flags [ : options ]
# Hook invoked automatically by LFlags. Applies `flags' also to module's test
# driver and to all directory-based drivers which incorporate module's test
# objects.
rule UnitTestLFlags
{
local mod = $(1) ;
local flags = $(2) ;
local options = $(3) ;
local unittests = $($(mod)_UNITTESTS) ;
local u ;
for u in $(unittests)
{
LFlags $(u) : $(flags) : $(options) ;
}
}
# UnitTestLibDepends module : deps
# Hook invoked automatically by LibDepends. Applies `deps' also to module's
# test driver and to all directory-based drivers which incorporate module's
# test objects.
rule UnitTestLibDepends
{
local mod = $(1) ;
local libs = $(2) ;
local unittests = $($(mod)_UNITTESTS) ;
local u ;
for u in $(unittests)
{
LinkWith $(u) : $(libs) ;
}
}
# UnitTestSource wrapper : files
# Create a `wrapper' which #includes all `files' (which are probably *.t test
# fragments or headers). Also scan `files' for subclasses of
# CppUnit::TestFixture and invoke CPPUNIT_TEST_SUITE_REGISTRATION() for each
# discovery.
actions UnitTestSource
{
cat <<EOF > $(<)
// Automatically generated; do not edit.
#include <string>
#include <cppunit/TestCaller.h>
#include <cppunit/TestFixture.h>
#include <cppunit/TestSuite.h>
#include <cppunit/extensions/HelperMacros.h>
$(UNITTEST_BOILERPLATE_TEST_PRE)
EOF
for i in $(>:BS); do
echo '#include "'$i'"' >> $(<)
done
for i in $(>); do
classes=`sed '/public[ ][ ]*CppUnit::TestFixture/!d;\
s/class[ ][ ]*\([^ ][^ ]*\)[ ]*:.*/\1/' < $i`
for c in $classes; do
echo "CPPUNIT_TEST_SUITE_REGISTRATION($c);" >> $(<)
done
done
cat <<EOF >> $(<)
$(UNITTEST_BOILERPLATE_TEST_POST)
EOF
}
# UnitTestMain file
# Create a generic main() which is used for all test driver programs. It
# uses CppUnit's automated test class discovery protocol to discover classes
# containing tests, therefore it is entirely generic and can be used by any
# number of driver programs.
actions UnitTestMain
{
cat <<EOF > $(<)
// Automatically generated; do not edit.
#include <string>
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/TestResult.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
$(UNITTEST_BOILERPLATE_MAIN_PRE)
int main(int argc, char** argv)
{
bool verbose = false;
for (int i = 1; i < argc; i++)
{
char const* s = argv[i];
if (*s == '-')
{
do { s++; } while (*s == '-');
verbose = (*s == 'v' || *s == 'V');
if (verbose)
break;
}
}
CppUnit::TextUi::TestRunner runner;
CppUnit::TestFactoryRegistry& registry =
CppUnit::TestFactoryRegistry::getRegistry();
CppUnit::BriefTestProgressListener listener;
if (verbose)
runner.eventManager().addListener(&listener);
runner.addTest(registry.makeTest());
return runner.run("", false, true, !verbose) ? 0 : -1;
}
$(UNITTEST_BOILERPLATE_MAIN_POST)
EOF
}
# UnitTestRun check_target : program
# Actually run the unit test driver `program' for the invocation target
# `check_target'.
actions UnitTestRun
{
$(>) $(UNITTEST_RUNFLAGS)
}
}
else # !CPPUNIT.AVAILABLE
{
rule UnitTest { }
rule UnitTestCFlags { }
rule UnitTestLFlags { }
rule UnitTestLibDepends { }
actions UnitTestDisabled
{
echo "$(<): Unit testing disabled (CppUnit not installed)."
}
Always check_all ;
Depends check : check_all ;
UnitTestDisabled check_all ;
}
NotFile check checkclean check_all check_allclean ;
Depends check : check_all ;
Depends clean : checkclean ;
Help check : "Run unit tests" ;