# (C) Copyright David Abrahams 2002. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) if ! $(.testing.jam-included) { .testing.jam-included = "}" ; # The brace makes emacs indentation happy # Decide which toolsets should be treated like an ordinary (unix) GCC installation gcc-compilers = [ MATCH ^(gcc.*)$ : $(TOOLS) ] ; mingw-compilers = [ MATCH ^(mingw.*)$ ^(gcc-nocygwin.*) : $(TOOLS) ] ; gcc-compilers = [ difference $(gcc-compilers) : $(mingw-compilers) ] ; local rule get-var-value ( name + ) { return $($(name)) ; } local rule get-library-name ( path_or_tokens + ) { local path = $(path_or_tokens) ; if ! $(path[1]) { path = [ split-path $(path) ] ; } path = /$(path:J=/) ; local match1 = [ MATCH /libs/(.*)/(test|example) : $(path) ] ; local match2 = [ MATCH /libs/(.*)$ : $(path) ] ; local match3 = [ MATCH (/status$) : $(path) ] ; if $(match1) { return $(match1[0]) ; } else if $(match2) { return $(match2[0]) ; } else if $(match3) { return "" ; } else if --dump-tests in $(ARGV) { EXIT Cannot extract library name from path $(path) ; } } # 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 ? : default-build * ) { if ! $(gIN_LIB_INCLUDE) # No need to execute this code twice { local result ; { # manufacture a test name if none supplied explicitly test-name ?= $(sources[1]:D=:S=) ; local library = "" ; if $(SUBDIR_TOKENS) { library = [ get-library-name $(SUBDIR_TOKENS) ] ; } # Make sure that targets don't become part of "all" local gSUPPRESS_FAKE_TARGETS = true ; result = [ declare-local-target $(test-name) : $(sources) : $(requirements) $(BOOST_ROOT) : $(default-build) : $(target-type) ] ; if $(result) && $(result) in $(.all-boost-tests) { EXIT boost-test \"$(result)\" declared twice ; .all-boost-tests += $(result) ; } if --dump-tests in $(ARGV) && $(result) { dump-test $(library) : $(result) : $(requirements) ; } } 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) ; # Make sure the test result doesn't hang around if the test fails RMOLD $(result) ; return $(result) ; } } # Helper for boost-test above. Uses dynamic scoping to access # boost-test's locals. local rule dump-test ( library ? : targets + : requirements * ) { local srcs = [ on $(targets) get-var-value source-files ] ; # Pick any source file names off the associated main target as well. srcs += [ unique [ on $(gTARGET_SUBVARIANT($(targets))) get-var-value source-files ] ] ; local pwd = [ PWD ] ; # locate each source file local source-files ; for local s in $(srcs) { # find out where to look for the file local paths = [ on $(s) get-var-value LOCATE SEARCH ] ; s = $(s:G=) ; # strip grist # build all the potential full paths of the file local full-paths = $(s:R=$(paths)) ; # look there local files = [ GLOB $(full-paths:D) : $(s:D=) ] ; if $(files) { # make relative to the project root instead of "." local file = $(files[1]:R=$(pwd)) ; # try to undo absolute paths source-files += [ relative-path $(file) ] ; } } # Extract values of the feature local dump-test-info = [ get-values : $(requirements) ] ; # Format them into a single string of quoted strings dump-test-info = \"$(dump-test-info:J=\"\ \")\" ; local test-id ; # unique among all tests if $(library) { test-id = $(library)/$(test-name) ; } else { test-id = $(test-name) ; } ECHO boost-test($(target-type)) \"$(test-id)\" [$(dump-test-info)] ":" \"$(source-files)\" ; if --dump-test-targets in $(ARGV) { ECHO boost-test(TARGET) \"$(test-id)\" [$(dump-test-info)] ":" \"$(targets)\" ; } } ####### BOOST_TEST_SUFFIX ?= .test ; # Set a variable which says how to dump a file to stdout if $(NT) { CATENATE = type ; } else { CATENATE = cat ; } actions **passed** bind source-files { echo $(source-files) > $(<) } actions (failed-as-expected) { echo failed as expected > $(<) } # 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 + ) { local grist = [ MATCH ^<(.*)> : $(dependency:G) ] ; local marker = $(dependency:G=$(grist)*fail) ; (failed-as-expected) $(marker) ; FAIL_EXPECTED $(dependency) ; MakeLocate $(marker) : $(LOCATE_TARGET) ; RMOLD $(marker) ; DEPENDS $(marker) : $(dependency) ; succeeded-test-file $(test-file) : $(marker) ; } # 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 + ) { **passed** $(test-file) ; DEPENDS $(test-file) : $(dependency) ; } 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) ; } # 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 test-file is erased upon failure, so as not # to give a false indication of success. RMOLD $(test-file) ; # Call dependency-type's generator function to attempt the build $(gGENERATOR_FUNCTION($(dependency-type))) $(dependency) : $(sources) : $(requirements) ; # Generator functions don't handle this job for us; perhaps they should. set-target-variables $(dependency) ; if $(test-file:S) != $(BOOST_TEST_SUFFIX) { EXIT unexpected test file suffix. Filename: $(test-file) ; } # The test files go with the other subvariant targets MakeLocate $(test-file) : $(LOCATE_TARGET) ; Clean clean : $(test-file) ; # Generate the test file $(test-file-generator) $(test-file) : $(dependency) ; } ### 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(TEST_EXE) = run-test EXE ; SUFTEST_EXE = .run ; rule test-executable(EXE) ( target-to-test ) { return $(target-to-test) ; } rule nt-paths-to-cygwin ( paths * ) { local result ; for local p in $(paths) { # if already rooted... if [ MATCH ^([A-Za-z]:[/\\]) : $(p) ] { p = [ split-path $(p) ] ; p = [ join-path /cygdrive [ MATCH ^(.).* : $(p[1]) ] $(p[2-]) ] ; } result += $(p:T) ; } return $(result) ; } rule run-test ( type-to-test run-target : sources * ) { local parent = $(run-target:S=.test) ; local targets-to-test ; local main-target = $(gTARGET_SUBVARIANT($(parent))) ; # If no sources, assume someone else already built the targets, # and they are the library dependencies of the test target that # match the type-to-test. This should perhaps be _all_ # dependencies, but hopefully we'll throw this code out soon! if ! $(sources) { for local t in [ on $(parent) return $(library-dependencies) ] { if $(gTARGET_TYPE($(t))) = $(type-to-test) { targets-to-test += $(t) ; } } } else { targets-to-test = $(run-target:S=$(SUF$(type-to-test))) ; set-target-variables $(targets-to-test) ; generate-dependencies $(main-target) : $(targets-to-test) ; declare-basic-target $(targets-to-test) : $(sources) : : : $(type-to-test) ; $(gGENERATOR_FUNCTION($(type-to-test))) $(targets-to-test) : $(sources) ; } # 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 $(run-target) : $(LOCATE_TARGET) ; local executable = [ test-executable($(type-to-test)) $(targets-to-test) ] ; local x-input-files = [ expand-source-names $(gRUN_TEST_INPUT_FILES) ] ; local x-input-deps = ; for local x-input-file in $(x-input-files) { local input-file-type = [ ungrist $(x-input-file:G:L) ] ; local input-file-type-id = $(gTARGET_TYPE_ID($(input-file-type))) ; local input-file-typed = $(x-input-file:G=$(input-file-type-id)) ; if $(input-file-typed) { dependent-include $(input-file-typed) ; local input-file-path = [ target-path-of $(input-file-typed) ] ; local input-file-target = [ target-id-of $(input-file-path) ] ; local [ protect-subproject ] ; enter-subproject [ directory-of $(input-file-path) ] ; local p = $(gBUILD_PROPERTIES) ; segregate-free-properties p ; local input-file-subvariant = [ find-compatible-subvariant $(input-file-target) : $(gCURRENT_TOOLSET) $(variant) : $(p) ] ; local input-file-dep = [ subvariant-target $(input-file-target) : $(input-file-subvariant) : $(gCURRENT_TOOLSET) $(variant) ] ; x-input-deps += $(input-file-dep) ; } else { x-input-deps += $(x-input-file) ; } } DEPENDS $(run-target) : $(executable) $(targets-to-test) $(x-input-deps) ; INPUT_FILES on $(run-target) = $(x-input-deps) ; ARGS on $(run-target) = $(gRUN_TEST_ARGS) ; ARGS2 on $(run-target) = $(gRUN_TEST_ARGS2) ; # # Prepare path setup # local path-sources = $(parent) $(executable) ; # where do we get paths from? local $(.run-path-shell-vars) ; # declare local path variables # initialize path variables from the path-sources for local v in $(.run-path-vars) { local shell-var = $(.shell-var($(v))) ; $(shell-var) = [ unique $($(shell-var)) $(gRUN_$(v)($(path-sources))) ] ; } local nt-cygwin ; if $(NT) && $(gCURRENT_TOOLSET) in $(gcc-compilers) { nt-cygwin = true ; } # build up a fragment of shell command local path-setup ; for local shell-var in $(.run-path-shell-vars) { if $($(shell-var)) { local dirs = $($(shell-var)) ; local splitpath = $(SPLITPATH) ; # PATH gets translated automatically; the rest must be # cygwin-native when running with Cygwin GCC under NT if $(nt-cygwin) && $(shell-var) != PATH { dirs = [ nt-paths-to-cygwin $(dirs) ] ; splitpath = ":" ; } if $(.run-path-shell-var-value($(shell-var))) { dirs += $(.env-prefix)$(shell-var)$(.env-suffix) ; } path-setup += $(SHELL_SET)$(shell-var)=$(dirs:J=$(splitpath)) ; path-setup += $(SHELL_EXPORT)$(shell-var) ; } } local debugger = [ MATCH ^--debugger=(.*) : $(ARGV) ] ; debugger = $(debugger)" " ; local newline = " " ; PATH_SETUP on $(run-target) = $(path-setup:J=$(newline):E=)$(newline)$(debugger:E=) ; local verbose-test = 1 ; if --verbose-test in $(ARGV) { verbose-test = 0 ; } VERBOSE_TEST on $(run-target) = $(verbose-test) ; if $(NT) { STATUS on $(run-target) = %status% ; SET_STATUS on $(run-target) = "set status=%ERRORLEVEL%" ; RUN_OUTPUT_NL on $(run-target) = "echo." ; STATUS_0 on $(run-target) = "%status% EQU 0 (" ; STATUS_NOT_0 on $(run-target) = "%status% NEQ 0 (" ; VERBOSE on $(run-target) = "%verbose% EQU 0 (" ; ENDIF on $(run-target) = ")" ; } else { STATUS on $(run-target) = "$status" ; SET_STATUS on $(run-target) = "status=$?" ; RUN_OUTPUT_NL on $(run-target) = "echo" ; STATUS_0 on $(run-target) = "test $status -eq 0 ; then" ; STATUS_NOT_0 on $(run-target) = "test $status -ne 0 ; then" ; VERBOSE on $(run-target) = "test $verbose -eq 0 ; then" ; ENDIF on $(run-target) = "fi" ; } capture-run-output $(run-target) : $(executable) : $(debugger) ; if ! ( --preserve-test-targets in $(ARGV) ) && ! [ MATCH ^--debugger=(.*) : $(ARGV) ] { RmTemps $(run-target) : $(targets-to-test) ; } } # The rule is just used for argument checking rule capture-run-output ( target : executable + : debugger ? ) { gTEST_OUTPUT_FILE($(target)) = $(target:S=.output) ; INCLUDES $(target) : $(target:S=.output) ; MakeLocate $(test-file:S=.output) : $(LOCATE_TARGET) ; Clean clean : $(test-file:S=.output) ; output-file on $(target) = $(target:S=.output) ; if $(debugger) { debug-test $(target) : $(executable) ; } else { execute-test $(target) : $(executable) ; } if --run-all-tests in $(ARGV) { ALWAYS $(target) ; } } if $(NT) { CATENATE = type ; } else { CATENATE = cat ; } actions debug-test bind INPUT_FILES { $(PATH_SETUP)$(>) $(ARGS) "$(INPUT_FILES)" $(ARGS2) } actions execute-test bind INPUT_FILES output-file { $(PATH_SETUP)$(>) $(ARGS) "$(INPUT_FILES)" $(ARGS2) > $(output-file) 2>&1 $(SET_STATUS) $(RUN_OUTPUT_NL) >> $(output-file) echo EXIT STATUS: $(STATUS) >> $(output-file) if $(STATUS_0) $(CP) $(output-file) $(<) $(ENDIF) $(SHELL_SET)verbose=$(VERBOSE_TEST) if $(STATUS_NOT_0) $(SHELL_SET)verbose=0 $(ENDIF) if $(VERBOSE) echo ====== BEGIN OUTPUT ====== $(CATENATE) $(output-file) echo ====== END OUTPUT ====== $(ENDIF) exit $(STATUS) } declare-build-fail-test RUN_FAIL : TEST_EXE ; declare-build-succeed-test RUN : TEST_EXE ; rule run ( sources + : args * : input-files * : requirements * : name ? : default-build * : args2 * ) { local gRUN_TEST_ARGS = $(args) ; local gRUN_TEST_ARGS2 = $(args2) ; local gRUN_TEST_INPUT_FILES = $(input-files) ; SEARCH on $(input-files) = $(SEARCH_SOURCE) ; return [ boost-test $(sources) : RUN : $(requirements) : $(name) : $(default-build) ] ; } rule run-fail ( sources + : args * : input-files * : requirements * : name ? ) { local gRUN_TEST_ARGS = $(2) ; local gRUN_TEST_INPUT_FILES = $(3) ; SEARCH on $(3) = $(SEARCH_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) ] ; } declare-build-succeed-test LINK : EXE ; rule link ( sources + : requirements * : name ? ) { return [ boost-test $(<) : LINK : $(2) : $(name) ] ; } ### Rules for grouping tests into suites: rule test-suite # pseudotarget-name : test-targets... { NOTFILE $(<) ; type-DEPENDS $(<) : $(>) ; } } # include guard