# (C) Copyright David Abrahams 2002. Permission to copy, use, modify, sell and # distribute this software is granted provided this copyright notice appears in # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. if ! $(.testing.jam-included) { .testing.jam-included = "}" ; # The brace makes emacs indentation happy ####################################################################################### # Tests generate a number of files reflecting their status in the subvariant-directory # # .test - a marker so that Jam knows when it needs to be # rebuilt. It will contain the paths from this # directory to the source files used for the test # # .success - present only if the test last succeeded. # Contains the string "succeeded" # # .failure - present only if the test last failed. Contains the string # "failed". # # .output - contains the output of the test if it was a run test. ####################################################################################### # Declares a test target. If name is not supplied, it is taken from the name of # the first source file, sans extension and directory path. # # type should be a target type (e.g. OBJ, DLL, LIB, EXE) # # RETURNS the name(s) of the generated test target(s). rule boost-test ( sources + : target-type : requirements * : test-name ? ) { local result ; { # manufacture a test name if none supplied explicitly test-name ?= $(sources[1]:D=:S=) ; # Make sure that targets don't become part of "all" local gSUPPRESS_FAKE_TARGETS = true ; local dependencies = [ select-gristed $(sources) ] ; local source-files = [ select-ungristed $(sources) ] ; result = [ declare-local-target $(test-name) : $(source-files) $(dependencies) # sources : $(requirements) $(BOOST_ROOT) # requirements : # default build : $(target-type) ] ; } Clean clean : $(result) ; # make NOTFILE targets of the same base name as the sources which can # be used to build a single test. type-DEPENDS $(sources:B:S=) : $(result) ; # The NOTFILE target called "test" builds all tests type-DEPENDS test : $(result) ; return $(result) ; } ####### BOOST_TEST_SUFFIX ?= .test ; # a utility rule which causes test-file to be built successfully only if # dependency fails to build. Used for expected-failure tests. rule failed-test-file ( test-file : dependency + ) { catenate-output-on-failure $(test-file) : $(dependency[1]) ; DEPENDS $(test-file) : $(dependency) ; NOCARE $(dependency) ; TEMPORARY $(dependency) ; FAIL_EXPECTED $(dependency) ; } # a utility rule which causes test-file to be built successfully only if # dependency builds. Used for expected-success tests. rule succeeded-test-file ( test-file : dependency + ) { catenate-output-on-failure $(test-file) : $(dependency[1]) ; DEPENDS $(test-file) : $(dependency) ; NOCARE $(dependency) ; TEMPORARY $(dependency) ; } rule catenate-output-on-failure ( test-file : dependency ) { if $(gTEST_OUTPUT_FILE($(dependency))) { output-file on $(test-file) = $(gTEST_OUTPUT_FILE($(dependency))) ; } } if $(NT) { CATENATE1 = "if exist \"" ; CATENATE2 = "\" ( ECHO *** output file follows *** type \"" ; CATENATE3 = "\" ECHO *** end of output file *** )" ; actions failed-test-file bind output-file source-files { echo "$(source-files)" > $(<:S=.test) if EXIST "$(>)". ( if EXIST "$(<:S=.success)". del /f/q "$(<:S=.success)". echo "failed" > "$(<:S=.failure)" $(CATENATE1)$(output-file)$(CATENATE2)$(output-file)$(CATENATE3) echo * echo ***************** failed above test: $(<:B:S=) ******************** echo * ) ELSE ( if EXIST "$(<:S=.failure)". del /f/q "$(<:S=.failure)". echo succeeded > "$(<:S=.success)" ) } actions succeeded-test-file bind output-file source-files { echo "$(source-files)" > $(<:S=.test) if EXIST "$(>)". ( if EXIST "$(<:S=.failure)". del /f/q "$(<:S=.failure)". echo succeeded > "$(<:S=.success)" ) ELSE ( if EXIST "$(<:S=.success)". del /f/q "$(<:S=.success)". echo failed > "$(<:S=.failure)" $(CATENATE1)$(output-file)$(CATENATE2)$(output-file)$(CATENATE3) echo * echo ***************** failed above test: $(<:B:S=) ******************** echo * ) } } else { CATENATE = "cat " ; CATENATE1 = "if [ -f \"" ; CATENATE2 = "\" ] ; then echo *** output file follows *** cat \"" ; CATENATE3 = "\" ; echo *** end of output file *** fi" ; actions failed-test-file bind output-file source-files { echo "$(source-files)" > $(<:S=.test) if [ -f "$(>)" ] ; then if [ -f "$(<:S=.success)" ] ; then $(RM) "$(<:S=.success)" fi echo "failed" > $(<:S=.failure) $(CATENATE1)$(output-file)$(CATENATE2)$(output-file)$(CATENATE3) echo "*" echo "***************** failed above test: $(<:B:S=) ********************" echo "*" else if [ -f "$(<:S=.failure)" ] ; then $(RM) "$(<:S=.failure)" fi echo "succeeded" > "$(<:S=.success)" fi } actions succeeded-test-file bind output-file source-files { echo "$(source-files)" > "$(<:S=.test)" if [ -f "$(>)" ] ; then if [ -f "$(<:S=.failure)" ] ; then $(RM) "$(<:S=.failure)" fi echo "succeeded" > "$(<:S=.success)" else if [ -f "$(<:S=.success)" ] ; then $(RM) "$(<:S=.success)" fi echo "failed" > "$(<:S=.failure)" $(CATENATE1)$(output-file)$(CATENATE2)$(output-file)$(CATENATE3) echo "*" echo "***************** failed above test: $(<:B:S=) ********************" echo "*" fi } } rule declare-build-succeed-test ( test-type : dependency-type ) { gGENERATOR_FUNCTION($(test-type)) = build-test succeeded-test-file ; gDEPENDENCY_TYPE($(test-type)) = $(dependency-type) ; SUF$(test-type) = $(BOOST_TEST_SUFFIX) ; } # A utility rule which declares test-type to be a target type which # depends on the /failed/ construction of a target of type # dependency-type. rule declare-build-fail-test ( test-type : dependency-type ) { gGENERATOR_FUNCTION($(test-type)) = build-test failed-test-file ; gDEPENDENCY_TYPE($(test-type)) = $(dependency-type) ; SUF$(test-type) = $(BOOST_TEST_SUFFIX) ; } # A temporary measure in case people don't rebuild their Jam # executables. Define the builtin RMOLD if it's missing. if ! ( RMOLD in [ RULENAMES ] ) { rule RMOLD { } } # When the appropriate generator function is bound to the # test-file-generator argument, this is a target generator function # for target types declared with declare-build-succeed-test and # declare-build-fail-test, above. rule build-test ( test-file-generator test-file + : sources + : requirements * ) { # Get the target type of the current target out of the build properties local target-type = [ get-values : $(gBUILD_PROPERTIES) ] ; # Get the type of target to attempt to build; the outcome of this # attempt determines the result of the test. local dependency-type = $(gDEPENDENCY_TYPE($(target-type))) ; # Get the actual name of the target to attempt to build local dependency = $(test-file[1]:S=$(SUF$(dependency-type))) ; # record the source file names so we can put them in the .test # file. source-files on $(test-file) = $(sources) ; # Make sure that the dependency is erased, so as not to give a # false indication of success. RMOLD $(dependency) ; # Call dependency-type's generator function to attempt the build local ignored = [ $(gGENERATOR_FUNCTION($(dependency-type))) $(dependency) : $(sources) : $(requirements) ] ; # Generator functions don't handle this job for us; perhaps they should. set-target-variables $(dependency) ; # The test files go with the other subvariant targets local test-files = $(test-file:S=.test) $(test-file:S=.success) $(test-file:S=.failure) ; MakeLocate $(test-files) : $(LOCATE_TARGET) ; Clean clean : $(test-files) ; # Generate the test file $(test-file-generator) $(test-file) : $(dependency) ; if $(RUN_ALL_TESTS) { ALWAYS $(test-file) ; } } ### Rules for testing whether a file compiles ### # Establish the rule which generates targets of type "OBJ". Should really go # into basic build system, but wasn't needed 'till now. gGENERATOR_FUNCTION(OBJ) = Object ; declare-build-fail-test COMPILE_FAIL : OBJ ; declare-build-succeed-test COMPILE : OBJ ; # Test that the given source-file(s) compile rule compile ( sources + : requirements * : test-name ? ) { return [ boost-test $(sources) : COMPILE : $(requirements) : $(test-name) ] ; } # Test that the given source-file(s) fail to compile rule compile-fail ( sources + : requirements * : test-name ? ) { return [ boost-test $(sources) : COMPILE_FAIL : $(requirements) : $(test-name) ] ; } ### Rules for testing whether a program runs ### gGENERATOR_FUNCTION(RUN_TEST) = run-test ; SUFRUN_TEST = .run ; rule run-test ( target : sources + : requirements * ) { local executable = $(target:S=$(SUFEXE)) ; local parent = $(target:S=.test) ; # Move important data from the test target to the executable gRUN_LD_LIBRARY_PATH($(executable)) = $(gRUN_LD_LIBRARY_PATH($(parent))) ; gRUN_PATH($(executable)) = $(gRUN_PATH($(parent))) ; executable-file $(executable) : $(sources) : $(requirements) ; set-target-variables $(executable) ; # The .test file goes with the other subvariant targets # normalization is a hack to get the slashes going the right way on Windoze local LOCATE_TARGET = [ FDirName [ split-path $(LOCATE_TARGET) ] ] ; MakeLocate $(target) : $(LOCATE_TARGET) ; DEPENDS $(target) : $(executable) $(gRUN_TEST_INPUT_FILES) ; INPUT_FILES on $(target) = $(gRUN_TEST_INPUT_FILES) ; ARGS on $(target) = $(gRUN_TEST_ARGS) ; capture-run-output $(target) : $(executable) ; } # The rule is just used for argument checking rule capture-run-output ( target : executable + ) { gTEST_OUTPUT_FILE($(target)) = $(target:S=.output) ; INCLUDES $(target) : $(target:S=.output) ; MakeLocate $(test-file:S=.failure) : $(LOCATE_TARGET) ; MakeLocate $(test-file:S=.output) : $(LOCATE_TARGET) ; Clean clean $(test-file:S=.output) ; capture-run-output1 $(target) : $(executable) ; } rule capture-run-output1 ( target : executable ) { output-file on $(target) = $(target:S=.output) ; local targets = $(target) $(target:S=.output) ; RUN_PATH on $(targets) = [ join $(gRUN_PATH($(executable))) : $(SPLITPATH) ] ; if $(UNIX) { RUN_LD_LIBRARY_PATH on $(targets) = [ join $(gRUN_LD_LIBRARY_PATH($(executable))) : $(SPLITPATH) ] ; } } if $(UNIX) { actions capture-run-output1 bind INPUT_FILES output-file { $(SHELL_SET)PATH=$(RUN_PATH:J=:):$PATH $(SHELL_EXPORT)PATH $(SHELL_SET)LD_LIBRARY_PATH=$(RUN_LD_LIBRARY_PATH):$LD_LIBRARY_PATH $(SHELL_EXPORT)LD_LIBRARY_PATH $(>) $(ARGS) $(INPUT_FILES) > $(output-file) 2>&1 && $(CP) $(output-file) $(<[1]) } } else { actions capture-run-output1 bind INPUT_FILES output-file { $(SHELL_SET)PATH=$(RUN_PATH:J=;);%PATH% $(SHELL_EXPORT)PATH $(>) $(ARGS) $(INPUT_FILES) > $(output-file) 2>&1 && $(CP) $(output-file) $(<[1]) } } declare-build-fail-test RUN_FAIL : RUN_TEST ; declare-build-succeed-test RUN : RUN_TEST ; rule run ( sources + : args * : input-files * : requirements * : name ? ) { local gRUN_TEST_ARGS = $(args) ; local gRUN_TEST_INPUT_FILES = $(input-files) ; SEARCH on $(input-files) = $(LOCATE_SOURCE) ; return [ boost-test $(sources) : RUN : $(requirements) : $(name) ] ; } rule run-fail ( sources + : args * : input-files * : requirements * : name ? ) { local gRUN_TEST_ARGS = $(2) ; local gRUN_TEST_INPUT_FILES = $(3) ; SEARCH on $(3) = $(LOCATE_SOURCE) ; return [ boost-test $(<) : RUN_FAIL : $(4) : $(name) ] ; } ### Rules for testing whether a program links declare-build-fail-test LINK_FAIL : EXE ; rule link-fail ( sources + : requirements * : name ? ) { return [ boost-test $(<) : LINK_FAIL : $(2) : $(name) ] ; } ### Rules for grouping tests into suites: rule test-suite # pseudotarget-name : test-targets... { NOTFILE $(<) ; type-DEPENDS $(<) : $(>) ; } } # include guard