#============================================================================== # Jam rules for unit testing with CppTest (http://cpptest.sourceforge.net/) # Copyright (C) 2005 by Eric Sunshine # # 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 ; ## ## 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 < $(<) // Automatically generated; do not edit. #include #include #include #include #include $(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 <> $(<) $(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 < $(<) // Automatically generated; do not edit. #include #include #include #include #include $(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" ;