From cb8c58b87ef3c3ad15c37863ebf9fc17de34eebb Mon Sep 17 00:00:00 2001 From: Vladimir Prus Date: Fri, 26 Sep 2003 07:03:55 +0000 Subject: [PATCH] First version of regression testing support in V2. * new/testing.jam: Loots of changes. * new/virtual-target.jam: (action.path): Handle property. [SVN r20188] --- new/testing.jam | 308 +++++++++++++++++++++++++++++++----- new/virtual-target.jam | 12 +- v2/build/virtual-target.jam | 12 +- v2/tools/testing.jam | 308 +++++++++++++++++++++++++++++++----- 4 files changed, 566 insertions(+), 74 deletions(-) diff --git a/new/testing.jam b/new/testing.jam index 99d9a437a..1cf83100e 100644 --- a/new/testing.jam +++ b/new/testing.jam @@ -1,67 +1,303 @@ -# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and +# (C) Copyright David Abrahams 2002. +# (C) Copyright Vladimir Prus 2002-2003. +# 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. -# Testing support. +# This module implements regression testing framework. It declares a number of +# main target rules, which perform some action, and if the results are ok, +# creates an output file. +# +# The exact list of rules is: +# 'compile' -- creates .test file if compilation of sources was successfull +# 'compile-fail' -- creates .test file if compilation of sources failed +# 'run' -- creates .test file is running of executable produced from +# sources was successfull. Also leaves behing .output file +# with the output from program run. +# 'run-fail' -- same as above, but .test file is created if running fails. +# 'unit-test' -- same as 'run', except that output is not stored. +# +# In all cases, except for 'unit-test', presense of .test file is an incication that +# the test passed. For more convenient reporting, you might want to use C++ Boost +# regression testing utilities, see +# http://www.boost.org/more/regression.html + +# Things to do/consider: +# - No-skipping on multi +# - Teach compiler_status handle Jamfile.v2. +# - Grab RUN_PATH logic from V1 testing.jam +# - Implement all the parameters to 'run': args/input_files +# - Add link/link-fail? +# - is not implemented, since in Como-specific, and it's not clear how +# to implement it +# - std::locale-support is not impelemted (it's used in some tests). + import targets ; import "class" : new ; import property ; import feature ; import toolset ; +import alias ; +import type ; +import generators ; +import project ; +import property-set ; +import virtual-target ; +import path ; + +rule init ( ) { } # The feature which controls the name of program used to # lanch test programs. feature.feature testing.launcher : : optional free ; +feature.feature location-prefix : : free ; +feature.feature test-info : : free incidental ; -class unit-test-target-class : typed-target +# Register target types. +type.register TEST : test ; +type.register COMPILE : : TEST : main ; +type.register COMPILE_FAIL : : TEST : main ; +type.register RUN_OUTPUT : run : : main ; +type.register RUN : : TEST : main ; +type.register RUN_FAIL : : TEST : main ; +type.register UNIT_TEST : passed : TEST : main ; + +# Declare the rules which create main targets. +# While the 'type' module already creates rules with the same names for us, +# we need extra convenience: default name of main target, so write +# our own versions. + +# Helper rule. Create a test target, using basename of first source of no +# target name is explicitly passed. Remembers the created target in +# a global variable. +rule make-test ( target-type : sources + : requirements * : target-name ? ) { - rule __init__ ( name : project : sources * : requirements * - : default-build * ) - { - typed-target.__init__ $(name) : $(project) : EXE : $(sources) - : $(requirements) : $(default-build) ; - } - - rule construct ( source-targets * : properties * ) - { - local result = - [ typed-target.construct $(source-targets) : $(properties) ] ; - local exe = $(result[1]) ; - local exe-action = [ $(exe).action ] ; - local timestamp = [ new file-target $(self.name) : : $(self.project) ] ; - $(timestamp).suffix "passed" ; + target-name ?= $(sources[1]:D=:S=) ; - local a = [ new action $(timestamp) : $(exe) : testing.run : - [ $(exe-action).properties-ps ] ] ; - $(timestamp).action $(a) ; - return $(timestamp) ; - } + local project = [ CALLER_MODULE 1 ] ; + # The forces the build system for generate paths in the form + # $build_dir/array1.test/gcc/debug + # This is necessary to allow post-processing tools to work. + local t = + [ targets.create-typed-target + [ type.type-from-rule-name $(target-type) ] : $(project) + : $(target-name) : $(sources) + : $(requirements) $(target-name).test ] ; + + # Remember the test (for --dump-test). + # A good way would be to collect all given a project. + # This has some technical problems: e.g. we can't call this dump from + # Jamfile since projects referred by 'build-project' are not available until + # whole Jamfile is loaded. + .all-tests += $(t) ; + return $(t) ; } -toolset.flags testing.run LAUNCHER ; +rule compile ( sources + : requirements * : target-name ? ) +{ + return [ make-test compile : $(sources) : $(requirements) : $(target-name) ] ; +} + +rule compile-fail ( sources + : requirements * : target-name ? ) +{ + return [ make-test compile-fail : $(sources) : $(requirements) : $(target-name) ] ; +} + +rule run ( sources + : args * : input-files * : requirements * : target-name ? + : default-build * ) +{ + if $(args) || $(input-files) || $(default-build) + { + EXIT "NOT supported" ; + } + return [ make-test run : $(sources) : $(requirements) : $(target-name) ] ; +} + +rule run-fail ( sources + : args * : input-files * : requirements * : target-name ? + : default-build * ) +{ + if $(args) || $(input-files) || $(default-build) + { + EXIT "NOT supported" ; + } + return [ make-test run-fail : $(sources) : $(requirements) : $(target-name) ] ; +} -actions run +# Rule for grouping tests in suites. +rule test-suite ( suite-name : tests + ) +{ + # In V2, if 'tests' are instances of 'abstract-target', they will be considered + # 'inline-targets' and will suffer some adjustments. This will not be compatible + # with V1 behaviour, so we get names of 'tests' and use them. + local names ; + for local t in $(tests) + { + names += [ $(t).name ] ; + } + modules.call-in [ CALLER_MODULE ] : alias $(suite-name) : $(names) ; +} + +# For all main target in 'project-module', +# which are typed target with type derived from 'TEST', +# produce some interesting information. +rule dump-tests # ( project-module ) +{ + for local t in $(.all-tests) + { + dump-test $(t) ; + } +} + +rule dump-test ( target ) +{ + local type = [ $(target).type ] ; + local name = [ $(target).name ] ; + local sources = [ $(target).sources ] ; + local source-files ; + for local s in $(sources) + { + if [ class.is-a $(s) : file-reference ] + { + source-files += + [ path.relative + [ path.root [ $(s).location ] [ path.pwd ] ] + /home/ghost/Work/boost ] ; + } + } + + local r = [ $(t).requirements ] ; + # Extract values of the feature + local test-info = [ $(r).get ] ; + + # Format them into a single string of quoted strings + test-info = \"$(test-info:J=\"\ \")\" ; + + ECHO boost-test($(type)) \"$(name)\" + [$(test-info)] + ":" \"$(source-files)\" + ; +} + +# Register generators. Depending on target type, either +# 'expect-success' or 'expect-failure' rule will be used. +generators.register-standard testing.expect-success : OBJ : COMPILE ; +generators.register-standard testing.expect-failure : OBJ : COMPILE_FAIL ; +generators.register-standard testing.expect-success : RUN_OUTPUT : RUN ; +generators.register-standard testing.expect-failure : RUN_OUTPUT : RUN_FAIL ; + +# Generator which runs an EXE and captures output. +generators.register-standard testing.capture-output : EXE : RUN_OUTPUT ; + +# Generator which creates target if sources runs successfully. +# Differers from RUN in that run output is not captured. +generators.register-standard testing.unit-test : EXE : UNIT_TEST ; + +# The action rules called by generators. + +# Causes the 'target' to exist after bjam invocation if and only if all the +# dependencies were successfully built. +rule expect-success ( target : dependency + : requirements * ) +{ + **passed** $(target) : $(sources) ; +} + +# Causes the 'target' to exist after bjam invocation if and only if all some +# of the dependencies were not successfully built. +rule expect-failure ( target : dependency + : properties * ) +{ + local grist = [ MATCH ^<(.*)> : $(dependency:G) ] ; + local marker = $(dependency:G=$(grist)*fail) ; + (failed-as-expected) $(marker) ; + FAIL_EXPECTED $(dependency) ; + LOCATE on $(marker) = [ on $(dependency) return $(LOCATE) ] ; + RMOLD $(marker) ; + DEPENDS $(marker) : $(dependency) ; + + DEPENDS $(target) : $(marker) ; + **passed** $(target) : $(marker) ; +} + +# The rule/action combination used to report successfull passing +# of a test. +rule **passed** +{ + # Dump all the tests, if needed. + # We do it here, since dump should happen after all Jamfiles are read, + # and there's no such place currently defined (but should). + if ! $(.dumped-tests) && --dump-tests in [ modules.peek : ARGV ] + { + .dumped-tests = true ; + dump-tests ; + } + + # Force deletion of the target, in case any dependencies failed + # to build. + RMOLD $(<) ; +} + +actions **passed** +{ + echo passed > $(<) +} + +actions (failed-as-expected) +{ + echo failed as expected > $(<) +} + +toolset.flags testing.unit-test LAUNCHER ; +actions unit-test { $(LAUNCHER) $(>) && touch $(<) } - -rule unit-test ( target-name : sources * : requirements * ) +rule capture-output ( target : source : properties * ) { - local project = [ CALLER_MODULE ] ; - - # TODO: what to do with default build? - targets.main-target-alternative - [ new unit-test-target-class $(target-name) : $(project) : $(sources) - : [ targets.main-target-requirements $(requirements) : $(project) ] - : [ targets.main-target-default-build $(default-build) : $(project) ] - ] ; + output-file on $(target) = $(target:S=.output) ; + LOCATE on $(target:S=.output) = [ on $(target) return $(LOCATE) ] ; + + # The INCLUDES kill a warning about independent target... + INCLUDES $(target) : $(target:S=.output) ; + # but it also puts .output into dependency graph, so we must tell jam + # it's OK if it cannot find the target or updating rule. + NOCARE $(target:S=.output) ; } -IMPORT $(__name__) : unit-test : : unit-test ; + +if $(NT) +{ + ENV_PATH = %PATH% ; + CATENATE = type ; +} +else +{ + ENV_PATH = $PATH ; + ENV_LD_LIBRARY_PATH = $LD_LIBRARY_PATH ; + CATENATE = cat ; + CP = cp ; +} +RUN_OUTPUT_HEADER = "echo ====== BEGIN OUTPUT ====== &&" ; +RUN_OUTPUT_FOOTER = " && echo ====== END OUTPUT ======" ; +if --verbose-test in $(ARGV) +{ + VERBOSE_CAT = "&& "$(RUN_OUTPUT_HEADER)" "$(CATENATE)" " ; +} + +actions capture-output bind INPUT_FILES output-file +{ + $(SHELL_SET)PATH=$(RUN_PATH:J=$(SPLITPATH)) + $(SHELL_EXPORT)$(PATH_VAR) + $(SHELL_SET)LD_LIBRARY_PATH=$(RUN_LD_LIBRARY_PATH:J=$(SPLITPATH)) + $(SHELL_EXPORT)$(LD_LIBRARY_PATH_VAR) + $(>) $(ARGS) "$(INPUT_FILES)" > $(output-file) 2>&1 && $(CP) $(output-file) $(<) $(VERBOSE_CAT)$(<)$(RUN_OUTPUT_FOOTER) || ( $(RUN_OUTPUT_HEADER) $(CATENATE) $(output-file) $(RUN_OUTPUT_FOOTER) && exit 1 ) +} + +IMPORT $(__name__) : compile compile-fail test-suite run run-fail + : : compile compile-fail test-suite run run-fail ; + diff --git a/new/virtual-target.jam b/new/virtual-target.jam index 502a6491c..a8a6b5ef1 100644 --- a/new/virtual-target.jam +++ b/new/virtual-target.jam @@ -551,7 +551,7 @@ IMPORT virtual-target : remember-binding : : virtual-target.remember-binding ; # not establish dependency relationship, but should do everything else. class action { - import type toolset property-set indirect class ; + import type toolset property-set indirect class path ; rule __init__ ( targets + : sources * : action-name + : property-set ? ) { @@ -655,6 +655,16 @@ class action rule path ( ) { local p = [ $(self.properties).as-path ] ; + # Really, an ugly hack. Boost regression test system requires + # specific target paths, and it seems that changing it to handle + # other directory layout is really hard. For that reason, + # we teach V2 to do the things regression system requires. + # The value o '' is predended to the path. + local prefix = [ $(self.properties).get ] ; + if $(prefix) + { + p = [ path.join $(prefix) $(p) ] ; + } return $(p) ; } diff --git a/v2/build/virtual-target.jam b/v2/build/virtual-target.jam index 502a6491c..a8a6b5ef1 100644 --- a/v2/build/virtual-target.jam +++ b/v2/build/virtual-target.jam @@ -551,7 +551,7 @@ IMPORT virtual-target : remember-binding : : virtual-target.remember-binding ; # not establish dependency relationship, but should do everything else. class action { - import type toolset property-set indirect class ; + import type toolset property-set indirect class path ; rule __init__ ( targets + : sources * : action-name + : property-set ? ) { @@ -655,6 +655,16 @@ class action rule path ( ) { local p = [ $(self.properties).as-path ] ; + # Really, an ugly hack. Boost regression test system requires + # specific target paths, and it seems that changing it to handle + # other directory layout is really hard. For that reason, + # we teach V2 to do the things regression system requires. + # The value o '' is predended to the path. + local prefix = [ $(self.properties).get ] ; + if $(prefix) + { + p = [ path.join $(prefix) $(p) ] ; + } return $(p) ; } diff --git a/v2/tools/testing.jam b/v2/tools/testing.jam index 99d9a437a..1cf83100e 100644 --- a/v2/tools/testing.jam +++ b/v2/tools/testing.jam @@ -1,67 +1,303 @@ -# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and +# (C) Copyright David Abrahams 2002. +# (C) Copyright Vladimir Prus 2002-2003. +# 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. -# Testing support. +# This module implements regression testing framework. It declares a number of +# main target rules, which perform some action, and if the results are ok, +# creates an output file. +# +# The exact list of rules is: +# 'compile' -- creates .test file if compilation of sources was successfull +# 'compile-fail' -- creates .test file if compilation of sources failed +# 'run' -- creates .test file is running of executable produced from +# sources was successfull. Also leaves behing .output file +# with the output from program run. +# 'run-fail' -- same as above, but .test file is created if running fails. +# 'unit-test' -- same as 'run', except that output is not stored. +# +# In all cases, except for 'unit-test', presense of .test file is an incication that +# the test passed. For more convenient reporting, you might want to use C++ Boost +# regression testing utilities, see +# http://www.boost.org/more/regression.html + +# Things to do/consider: +# - No-skipping on multi +# - Teach compiler_status handle Jamfile.v2. +# - Grab RUN_PATH logic from V1 testing.jam +# - Implement all the parameters to 'run': args/input_files +# - Add link/link-fail? +# - is not implemented, since in Como-specific, and it's not clear how +# to implement it +# - std::locale-support is not impelemted (it's used in some tests). + import targets ; import "class" : new ; import property ; import feature ; import toolset ; +import alias ; +import type ; +import generators ; +import project ; +import property-set ; +import virtual-target ; +import path ; + +rule init ( ) { } # The feature which controls the name of program used to # lanch test programs. feature.feature testing.launcher : : optional free ; +feature.feature location-prefix : : free ; +feature.feature test-info : : free incidental ; -class unit-test-target-class : typed-target +# Register target types. +type.register TEST : test ; +type.register COMPILE : : TEST : main ; +type.register COMPILE_FAIL : : TEST : main ; +type.register RUN_OUTPUT : run : : main ; +type.register RUN : : TEST : main ; +type.register RUN_FAIL : : TEST : main ; +type.register UNIT_TEST : passed : TEST : main ; + +# Declare the rules which create main targets. +# While the 'type' module already creates rules with the same names for us, +# we need extra convenience: default name of main target, so write +# our own versions. + +# Helper rule. Create a test target, using basename of first source of no +# target name is explicitly passed. Remembers the created target in +# a global variable. +rule make-test ( target-type : sources + : requirements * : target-name ? ) { - rule __init__ ( name : project : sources * : requirements * - : default-build * ) - { - typed-target.__init__ $(name) : $(project) : EXE : $(sources) - : $(requirements) : $(default-build) ; - } - - rule construct ( source-targets * : properties * ) - { - local result = - [ typed-target.construct $(source-targets) : $(properties) ] ; - local exe = $(result[1]) ; - local exe-action = [ $(exe).action ] ; - local timestamp = [ new file-target $(self.name) : : $(self.project) ] ; - $(timestamp).suffix "passed" ; + target-name ?= $(sources[1]:D=:S=) ; - local a = [ new action $(timestamp) : $(exe) : testing.run : - [ $(exe-action).properties-ps ] ] ; - $(timestamp).action $(a) ; - return $(timestamp) ; - } + local project = [ CALLER_MODULE 1 ] ; + # The forces the build system for generate paths in the form + # $build_dir/array1.test/gcc/debug + # This is necessary to allow post-processing tools to work. + local t = + [ targets.create-typed-target + [ type.type-from-rule-name $(target-type) ] : $(project) + : $(target-name) : $(sources) + : $(requirements) $(target-name).test ] ; + + # Remember the test (for --dump-test). + # A good way would be to collect all given a project. + # This has some technical problems: e.g. we can't call this dump from + # Jamfile since projects referred by 'build-project' are not available until + # whole Jamfile is loaded. + .all-tests += $(t) ; + return $(t) ; } -toolset.flags testing.run LAUNCHER ; +rule compile ( sources + : requirements * : target-name ? ) +{ + return [ make-test compile : $(sources) : $(requirements) : $(target-name) ] ; +} + +rule compile-fail ( sources + : requirements * : target-name ? ) +{ + return [ make-test compile-fail : $(sources) : $(requirements) : $(target-name) ] ; +} + +rule run ( sources + : args * : input-files * : requirements * : target-name ? + : default-build * ) +{ + if $(args) || $(input-files) || $(default-build) + { + EXIT "NOT supported" ; + } + return [ make-test run : $(sources) : $(requirements) : $(target-name) ] ; +} + +rule run-fail ( sources + : args * : input-files * : requirements * : target-name ? + : default-build * ) +{ + if $(args) || $(input-files) || $(default-build) + { + EXIT "NOT supported" ; + } + return [ make-test run-fail : $(sources) : $(requirements) : $(target-name) ] ; +} -actions run +# Rule for grouping tests in suites. +rule test-suite ( suite-name : tests + ) +{ + # In V2, if 'tests' are instances of 'abstract-target', they will be considered + # 'inline-targets' and will suffer some adjustments. This will not be compatible + # with V1 behaviour, so we get names of 'tests' and use them. + local names ; + for local t in $(tests) + { + names += [ $(t).name ] ; + } + modules.call-in [ CALLER_MODULE ] : alias $(suite-name) : $(names) ; +} + +# For all main target in 'project-module', +# which are typed target with type derived from 'TEST', +# produce some interesting information. +rule dump-tests # ( project-module ) +{ + for local t in $(.all-tests) + { + dump-test $(t) ; + } +} + +rule dump-test ( target ) +{ + local type = [ $(target).type ] ; + local name = [ $(target).name ] ; + local sources = [ $(target).sources ] ; + local source-files ; + for local s in $(sources) + { + if [ class.is-a $(s) : file-reference ] + { + source-files += + [ path.relative + [ path.root [ $(s).location ] [ path.pwd ] ] + /home/ghost/Work/boost ] ; + } + } + + local r = [ $(t).requirements ] ; + # Extract values of the feature + local test-info = [ $(r).get ] ; + + # Format them into a single string of quoted strings + test-info = \"$(test-info:J=\"\ \")\" ; + + ECHO boost-test($(type)) \"$(name)\" + [$(test-info)] + ":" \"$(source-files)\" + ; +} + +# Register generators. Depending on target type, either +# 'expect-success' or 'expect-failure' rule will be used. +generators.register-standard testing.expect-success : OBJ : COMPILE ; +generators.register-standard testing.expect-failure : OBJ : COMPILE_FAIL ; +generators.register-standard testing.expect-success : RUN_OUTPUT : RUN ; +generators.register-standard testing.expect-failure : RUN_OUTPUT : RUN_FAIL ; + +# Generator which runs an EXE and captures output. +generators.register-standard testing.capture-output : EXE : RUN_OUTPUT ; + +# Generator which creates target if sources runs successfully. +# Differers from RUN in that run output is not captured. +generators.register-standard testing.unit-test : EXE : UNIT_TEST ; + +# The action rules called by generators. + +# Causes the 'target' to exist after bjam invocation if and only if all the +# dependencies were successfully built. +rule expect-success ( target : dependency + : requirements * ) +{ + **passed** $(target) : $(sources) ; +} + +# Causes the 'target' to exist after bjam invocation if and only if all some +# of the dependencies were not successfully built. +rule expect-failure ( target : dependency + : properties * ) +{ + local grist = [ MATCH ^<(.*)> : $(dependency:G) ] ; + local marker = $(dependency:G=$(grist)*fail) ; + (failed-as-expected) $(marker) ; + FAIL_EXPECTED $(dependency) ; + LOCATE on $(marker) = [ on $(dependency) return $(LOCATE) ] ; + RMOLD $(marker) ; + DEPENDS $(marker) : $(dependency) ; + + DEPENDS $(target) : $(marker) ; + **passed** $(target) : $(marker) ; +} + +# The rule/action combination used to report successfull passing +# of a test. +rule **passed** +{ + # Dump all the tests, if needed. + # We do it here, since dump should happen after all Jamfiles are read, + # and there's no such place currently defined (but should). + if ! $(.dumped-tests) && --dump-tests in [ modules.peek : ARGV ] + { + .dumped-tests = true ; + dump-tests ; + } + + # Force deletion of the target, in case any dependencies failed + # to build. + RMOLD $(<) ; +} + +actions **passed** +{ + echo passed > $(<) +} + +actions (failed-as-expected) +{ + echo failed as expected > $(<) +} + +toolset.flags testing.unit-test LAUNCHER ; +actions unit-test { $(LAUNCHER) $(>) && touch $(<) } - -rule unit-test ( target-name : sources * : requirements * ) +rule capture-output ( target : source : properties * ) { - local project = [ CALLER_MODULE ] ; - - # TODO: what to do with default build? - targets.main-target-alternative - [ new unit-test-target-class $(target-name) : $(project) : $(sources) - : [ targets.main-target-requirements $(requirements) : $(project) ] - : [ targets.main-target-default-build $(default-build) : $(project) ] - ] ; + output-file on $(target) = $(target:S=.output) ; + LOCATE on $(target:S=.output) = [ on $(target) return $(LOCATE) ] ; + + # The INCLUDES kill a warning about independent target... + INCLUDES $(target) : $(target:S=.output) ; + # but it also puts .output into dependency graph, so we must tell jam + # it's OK if it cannot find the target or updating rule. + NOCARE $(target:S=.output) ; } -IMPORT $(__name__) : unit-test : : unit-test ; + +if $(NT) +{ + ENV_PATH = %PATH% ; + CATENATE = type ; +} +else +{ + ENV_PATH = $PATH ; + ENV_LD_LIBRARY_PATH = $LD_LIBRARY_PATH ; + CATENATE = cat ; + CP = cp ; +} +RUN_OUTPUT_HEADER = "echo ====== BEGIN OUTPUT ====== &&" ; +RUN_OUTPUT_FOOTER = " && echo ====== END OUTPUT ======" ; +if --verbose-test in $(ARGV) +{ + VERBOSE_CAT = "&& "$(RUN_OUTPUT_HEADER)" "$(CATENATE)" " ; +} + +actions capture-output bind INPUT_FILES output-file +{ + $(SHELL_SET)PATH=$(RUN_PATH:J=$(SPLITPATH)) + $(SHELL_EXPORT)$(PATH_VAR) + $(SHELL_SET)LD_LIBRARY_PATH=$(RUN_LD_LIBRARY_PATH:J=$(SPLITPATH)) + $(SHELL_EXPORT)$(LD_LIBRARY_PATH_VAR) + $(>) $(ARGS) "$(INPUT_FILES)" > $(output-file) 2>&1 && $(CP) $(output-file) $(<) $(VERBOSE_CAT)$(<)$(RUN_OUTPUT_FOOTER) || ( $(RUN_OUTPUT_HEADER) $(CATENATE) $(output-file) $(RUN_OUTPUT_FOOTER) && exit 1 ) +} + +IMPORT $(__name__) : compile compile-fail test-suite run run-fail + : : compile compile-fail test-suite run run-fail ; +