Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r64592 - in trunk/tools/build/v2: . build kernel tools util
From: ghost_at_[hidden]
Date: 2010-08-04 06:21:39


Author: vladimir_prus
Date: 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
New Revision: 64592
URL: http://svn.boost.org/trac/boost/changeset/64592

Log:
Port tools/testing.jam. This was scary.

Added:
   trunk/tools/build/v2/tools/testing-aux.jam (contents, props changed)
   trunk/tools/build/v2/tools/testing.py (contents, props changed)
Text files modified:
   trunk/tools/build/v2/build/engine.py | 16 ++++++++++++----
   trunk/tools/build/v2/build/errors.py | 5 +----
   trunk/tools/build/v2/build/generators.py | 8 ++++----
   trunk/tools/build/v2/build/targets.py | 2 +-
   trunk/tools/build/v2/build/toolset.py | 3 +--
   trunk/tools/build/v2/build/type.py | 5 +++++
   trunk/tools/build/v2/build_system.py | 9 +++++----
   trunk/tools/build/v2/kernel/bootstrap.jam | 4 ++--
   trunk/tools/build/v2/tools/common.py | 29 ++++++++++++++++++++++++++---
   trunk/tools/build/v2/util/__init__.py | 7 +++++++
   trunk/tools/build/v2/util/option.py | 2 +-
   trunk/tools/build/v2/util/os_j.py | 7 +++++--
   12 files changed, 70 insertions(+), 27 deletions(-)

Modified: trunk/tools/build/v2/build/engine.py
==============================================================================
--- trunk/tools/build/v2/build/engine.py (original)
+++ trunk/tools/build/v2/build/engine.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -32,12 +32,20 @@
                             targets, sources, [])
 
 class BjamNativeAction:
- """Class representing bjam action fully defined by Jam code."""
+ """Class representing bjam action defined by Jam code.
+
+ We still allow to associate a Python callable that will
+ be called when this action is installed on any target.
+ """
     
- def __init__(self, action_name):
+ def __init__(self, action_name, function):
         self.action_name = action_name
+ self.function = function
         
     def __call__(self, targets, sources, property_set):
+ if self.function:
+ self.function(targets, sources, property_set)
+
         p = []
         if property_set:
             p = property_set.raw()
@@ -138,7 +146,7 @@
         else:
             return context_module + '%' + action_name
 
- def register_bjam_action (self, action_name):
+ def register_bjam_action (self, action_name, function=None):
         """Informs self that 'action_name' is declared in bjam.
 
         From this point, 'action_name' is a valid argument to the
@@ -151,7 +159,7 @@
         # can just register them without specially checking if
         # action is already registered.
         if not self.actions.has_key(action_name):
- self.actions[action_name] = BjamNativeAction(action_name)
+ self.actions[action_name] = BjamNativeAction(action_name, function)
     
     # Overridables
 

Modified: trunk/tools/build/v2/build/errors.py
==============================================================================
--- trunk/tools/build/v2/build/errors.py (original)
+++ trunk/tools/build/v2/build/errors.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -60,10 +60,7 @@
     def report(self):
         print "error:", self.args[0]
         if self.original_exception_:
- try:
- print format(self.original_exception_.args[0], " ")
- except:
- print format(str(self.original_exception_), " ")
+ print format(str(self.original_exception_), " ")
         print
         print " error context (most recent first):"
         for c in self.context_[::-1]:

Modified: trunk/tools/build/v2/build/generators.py
==============================================================================
--- trunk/tools/build/v2/build/generators.py (original)
+++ trunk/tools/build/v2/build/generators.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -581,20 +581,20 @@
         real_source_type = source.type ()
 
         # If there are no source types, we can consume anything
- source_types = self.source_types
+ source_types = self.source_types()
         if not source_types:
- source_types = [real_source_type]
+ source_types = [real_source_type]
 
         consumed = []
         missing_types = []
- for st in self.source_types_:
+ for st in source_types:
             # The 'source' if of right type already)
             if real_source_type == st or type.is_derived (real_source_type, st):
                 consumed.append (source)
 
             else:
                missing_types.append (st)
-
+
         return (consumed, missing_types)
     
     def action_class (self):

Modified: trunk/tools/build/v2/build/targets.py
==============================================================================
--- trunk/tools/build/v2/build/targets.py (original)
+++ trunk/tools/build/v2/build/targets.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -825,7 +825,7 @@
         if self.source_targets_ == None:
             self.source_targets_ = []
             for s in self.sources_:
- self.source_targets_.append (self.resolve_reference (s, self.project_))
+ self.source_targets_.append(self.resolve_reference(s, self.project_)[0])
 
         return self.source_targets_
 

Modified: trunk/tools/build/v2/build/toolset.py
==============================================================================
--- trunk/tools/build/v2/build/toolset.py (original)
+++ trunk/tools/build/v2/build/toolset.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -329,8 +329,7 @@
             if f.dependency():
                 # the value of a dependency feature is a target
                 # and must be actualized
- # FIXME: verify that 'find' actually works, ick!
- result.append (manager.targets ().find(value).actualize ())
+ result.append(value.actualize())
 
             elif f.path() or f.free():
                 

Modified: trunk/tools/build/v2/build/type.py
==============================================================================
--- trunk/tools/build/v2/build/type.py (original)
+++ trunk/tools/build/v2/build/type.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -109,6 +109,11 @@
     from b2.manager import get_manager
     get_manager().projects().project_rules().add_rule_for_type(type)
 
+# FIXME: quick hack.
+def type_from_rule_name(rule_name):
+ return rule_name.upper().replace("-", "_")
+
+
 def register_suffixes (suffixes, type):
     """ Specifies that targets with suffix from 'suffixes' have the type 'type'.
         If a different type is already specified for any of syffixes, issues an error.

Modified: trunk/tools/build/v2/build_system.py
==============================================================================
--- trunk/tools/build/v2/build_system.py (original)
+++ trunk/tools/build/v2/build_system.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -104,10 +104,10 @@
     default_toolset_version = version
 
     
-pre_build_hook = None
+pre_build_hook = []
 
-def set_pre_build_hook(callable):
- pre_build_hook = callable
+def add_pre_build_hook(callable):
+ pre_build_hook.append(callable)
 
 post_build_hook = None
 
@@ -851,7 +851,8 @@
         #configure.print-configure-checks-summary ;
 
         if pre_build_hook:
- pre_build_hook()
+ for h in pre_build_hook:
+ h()
 
         bjam.call("DEPENDS", "all", actual_targets)
         ok = bjam.call("UPDATE_NOW", "all") # FIXME: add out-xml

Modified: trunk/tools/build/v2/kernel/bootstrap.jam
==============================================================================
--- trunk/tools/build/v2/kernel/bootstrap.jam (original)
+++ trunk/tools/build/v2/kernel/bootstrap.jam 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -212,9 +212,9 @@
                 }
             }
 
- rule get-target-variable ( target : variable )
+ rule get-target-variable ( targets + : variable )
             {
- return [ on $(target) return $($(variable)) ] ;
+ return [ on $(targets) return $($(variable)) ] ;
             }
 
             rule import-rules-from-parent ( parent-module : this-module : user-rules )

Modified: trunk/tools/build/v2/tools/common.py
==============================================================================
--- trunk/tools/build/v2/tools/common.py (original)
+++ trunk/tools/build/v2/tools/common.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -14,6 +14,7 @@
 import bjam
 import os
 import os.path
+import sys
 
 from b2.build import feature
 from b2.util.utility import *
@@ -46,9 +47,31 @@
 
     __debug_configuration = '--debug-configuration' in bjam.variable('ARGV')
     __show_configuration = '--show-configuration' in bjam.variable('ARGV')
-
+
+ global __executable_path_variable
+ OS = bjam.call("peek", [], "OS")[0]
+ if OS == "NT":
+ # On Windows the case and capitalization of PATH is not always predictable, so
+ # let's find out what variable name was really set.
+ for n in sys.environ:
+ if n.lower() == "path":
+ __executable_path_variable = n
+ break
+ else:
+ __executable_path_variable = "PATH"
+
+ m = {"NT": __executable_path_variable,
+ "CYGWIN": "PATH",
+ "MACOSX": "DYLD_LIBRARY_PATH",
+ "AIX": "LIBPATH"}
+ global __shared_library_path_variable
+ __shared_library_path_variable = m.get(OS, "LD_LIBRARY_PATH")
+
 reset()
 
+def shared_library_path_variable():
+ return __shared_library_path_variable
+
 # ported from trunk_at_47174
 class Configurations(object):
     """
@@ -502,9 +525,9 @@
     """
         Returns a command that prepends the given paths to the named path variable on
         the current platform.
- """
+ """
     return path_variable_setting_command(variable,
- paths + os.environ(variable).split(os.pathsep))
+ paths + os.environ.get(variable, "").split(os.pathsep))
 
 def file_creation_command():
     """

Added: trunk/tools/build/v2/tools/testing-aux.jam
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/testing-aux.jam 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -0,0 +1,216 @@
+# This module is imported by testing.py. The definitions here are
+# too tricky to do in Python
+
+# 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) ;
+}
+IMPORT testing : expect-success : : testing.expect-success ;
+
+# 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) ;
+}
+IMPORT testing : expect-failure : : testing.expect-failure ;
+
+# The rule/action combination used to report successful passing of a test.
+#
+rule **passed**
+{
+ # Force deletion of the target, in case any dependencies failed to build.
+ RMOLD $(<) ;
+}
+
+
+# Used to create test files signifying passed tests.
+#
+actions **passed**
+{
+ echo passed > "$(<)"
+}
+
+
+# Used to create replacement object files that do not get created during tests
+# that are expected to fail.
+#
+actions (failed-as-expected)
+{
+ echo failed as expected > "$(<)"
+}
+
+# Runs executable 'sources' and stores stdout in file 'target'. Unless
+# --preserve-test-targets command line option has been specified, removes the
+# executable. The 'target-to-remove' parameter controls what should be removed:
+# - if 'none', does not remove anything, ever
+# - if empty, removes 'source'
+# - if non-empty and not 'none', contains a list of sources to remove.
+#
+rule capture-output ( target : source : properties * : targets-to-remove * )
+{
+ 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 is
+ # OK if it cannot find the target or updating rule.
+ NOCARE $(target:S=.output) ;
+
+ # This has two-fold effect. First it adds input files to the dependendency
+ # graph, preventing a warning. Second, it causes input files to be bound
+ # before target is created. Therefore, they are bound using SEARCH setting
+ # on them and not LOCATE setting of $(target), as in other case (due to jam
+ # bug).
+ DEPENDS $(target) : [ on $(target) return $(INPUT_FILES) ] ;
+
+ if $(targets-to-remove) = none
+ {
+ targets-to-remove = ;
+ }
+ else if ! $(targets-to-remove)
+ {
+ targets-to-remove = $(source) ;
+ }
+
+ if [ on $(target) return $(REMOVE_TEST_TARGETS) ]
+ {
+ TEMPORARY $(targets-to-remove) ;
+ # Set a second action on target that will be executed after capture
+ # output action. The 'RmTemps' rule has the 'ignore' modifier so it is
+ # always considered succeeded. This is needed for 'run-fail' test. For
+ # that test the target will be marked with FAIL_EXPECTED, and without
+ # 'ignore' successful execution will be negated and be reported as
+ # failure. With 'ignore' we do not detect a case where removing files
+ # fails, but it is not likely to happen.
+ RmTemps $(target) : $(targets-to-remove) ;
+ }
+}
+
+
+if [ os.name ] = NT
+{
+ .STATUS = %status% ;
+ .SET_STATUS = "set status=%ERRORLEVEL%" ;
+ .RUN_OUTPUT_NL = "echo." ;
+ .STATUS_0 = "%status% EQU 0 (" ;
+ .STATUS_NOT_0 = "%status% NEQ 0 (" ;
+ .VERBOSE = "%verbose% EQU 1 (" ;
+ .ENDIF = ")" ;
+ .SHELL_SET = "set " ;
+ .CATENATE = type ;
+ .CP = copy ;
+}
+else
+{
+ .STATUS = "$status" ;
+ .SET_STATUS = "status=$?" ;
+ .RUN_OUTPUT_NL = "echo" ;
+ .STATUS_0 = "test $status -eq 0 ; then" ;
+ .STATUS_NOT_0 = "test $status -ne 0 ; then" ;
+ .VERBOSE = "test $verbose -eq 1 ; then" ;
+ .ENDIF = "fi" ;
+ .SHELL_SET = "" ;
+ .CATENATE = cat ;
+ .CP = cp ;
+}
+
+
+.VERBOSE_TEST = 0 ;
+if --verbose-test in [ modules.peek : ARGV ]
+{
+ .VERBOSE_TEST = 1 ;
+}
+
+
+.RM = [ common.rm-command ] ;
+
+
+actions capture-output bind INPUT_FILES output-file
+{
+ $(PATH_SETUP)
+ $(LAUNCHER) "$(>)" $(ARGS) "$(INPUT_FILES)" > "$(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=1
+ $(.ENDIF)
+ if $(.VERBOSE)
+ echo ====== BEGIN OUTPUT ======
+ $(.CATENATE) "$(output-file)"
+ echo ====== END OUTPUT ======
+ $(.ENDIF)
+ exit $(.STATUS)
+}
+
+IMPORT testing : capture-output : : testing.capture-output ;
+
+
+actions quietly updated ignore piecemeal together RmTemps
+{
+ $(.RM) "$(>)"
+}
+
+
+.MAKE_FILE = [ common.file-creation-command ] ;
+
+rule unit-test ( target : source : properties * )
+{
+ run-path-setup $(target) : $(source) : $(properties) ;
+}
+
+
+actions unit-test
+{
+ $(PATH_SETUP)
+ $(LAUNCHER) $(>) $(ARGS) && $(.MAKE_FILE) $(<)
+}
+
+rule record-time ( target : source : start end user system )
+{
+ local src-string = [$(source:G=:J=",")"] " ;
+ USER_TIME on $(target) += $(src-string)$(user) ;
+ SYSTEM_TIME on $(target) += $(src-string)$(system) ;
+}
+
+# Calling this rule requests that Boost Build time how long it taks to build the
+# 'source' target and display the results both on the standard output and in the
+# 'target' file.
+#
+rule time ( target : source : properties * )
+{
+ # Set up rule for recording timing information.
+ __TIMING_RULE__ on $(source) = testing.record-time $(target) ;
+
+ # Make sure that the source is rebuilt any time we need to retrieve that
+ # information.
+ REBUILDS $(target) : $(source) ;
+}
+
+
+actions time
+{
+ echo user: $(USER_TIME)
+ echo system: $(SYSTEM_TIME)
+
+ echo user: $(USER_TIME)" seconds" > "$(<)"
+ echo system: $(SYSTEM_TIME)" seconds" >> "$(<)"
+}

Added: trunk/tools/build/v2/tools/testing.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/testing.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -0,0 +1,339 @@
+# Status: ported, except for --out-xml
+# Base revision: 64488
+#
+# Copyright 2005 Dave Abrahams
+# Copyright 2002, 2003, 2004, 2005, 2010 Vladimir Prus
+# 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)
+
+# 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
+# successful.
+# 'compile-fail' -- creates .test file if compilation of sources failed.
+# 'run' -- creates .test file is running of executable produced from
+# sources was successful. Also leaves behind .output file
+# with the output from program run.
+# 'run-fail' -- same as above, but .test file is created if running fails.
+#
+# In all cases, presence of .test file is an indication 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).
+#
+# For historical reason, a 'unit-test' rule is available which has the same
+# syntax as 'exe' and behaves just like 'run'.
+
+# Things to do:
+# - Teach compiler_status handle Jamfile.v2.
+# Notes:
+# - <no-warn> is not implemented, since it is Como-specific, and it is not
+# clear how to implement it
+# - std::locale-support is not implemented (it is used in one test).
+
+import b2.build.feature as feature
+import b2.build.type as type
+import b2.build.targets as targets
+import b2.build.generators as generators
+import b2.build.toolset as toolset
+import b2.tools.common as common
+import b2.util.option as option
+import b2.build_system as build_system
+
+
+
+from b2.manager import get_manager
+from b2.util import stem, bjam_signature
+from b2.util.sequence import unique
+
+import bjam
+
+import re
+import os.path
+import sys
+
+def init():
+ pass
+
+# Feature controling the command used to lanch test programs.
+feature.feature("testing.launcher", [], ["free", "optional"])
+
+feature.feature("test-info", [], ["free", "incidental"])
+feature.feature("testing.arg", [], ["free", "incidental"])
+feature.feature("testing.input-file", [], ["free", "dependency"])
+
+feature.feature("preserve-test-targets", ["on", "off"], ["incidental", "propagated"])
+
+# Register target types.
+type.register("TEST", ["test"])
+type.register("COMPILE", [], "TEST")
+type.register("COMPILE_FAIL", [], "TEST")
+
+type.register("RUN_OUTPUT", ["run"])
+type.register("RUN", [], "TEST")
+type.register("RUN_FAIL", [], "TEST")
+
+type.register("LINK", [], "TEST")
+type.register("LINK_FAIL", [], "TEST")
+type.register("UNIT_TEST", ["passed"], "TEST")
+
+__all_tests = []
+
+# 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 if no target
+# name is explicitly passed. Remembers the created target in a global variable.
+def make_test(target_type, sources, requirements, target_name=None):
+
+ if not target_name:
+ target_name = stem(os.path.basename(sources[0]))
+
+ # Having periods (".") in the target name is problematic because the typed
+ # generator will strip the suffix and use the bare name for the file
+ # targets. Even though the location-prefix averts problems most times it
+ # does not prevent ambiguity issues when referring to the test targets. For
+ # example when using the XML log output. So we rename the target to remove
+ # the periods, and provide an alias for users.
+ real_name = target_name.replace(".", "~")
+
+ project = get_manager().projects().current()
+ # The <location-prefix> 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.
+ t = get_manager().targets().create_typed_target(
+ type.type_from_rule_name(target_type), project, real_name, sources,
+ requirements + ["<location-prefix>" + real_name + ".test"], [], [])
+
+ # The alias to the real target, per period replacement above.
+ if real_name != target_name:
+ get_manager().projects().project_rules().all_names_["alias"](
+ target_name, [t])
+
+ # Remember the test (for --dump-tests). A good way would be to collect all
+ # given a project. This has some technical problems: e.g. we can not call
+ # this dump from a Jamfile since projects referred by 'build-project' are
+ # not available until the whole Jamfile has been loaded.
+ __all_tests.append(t)
+ return t
+
+
+# Note: passing more that one cpp file here is known to fail. Passing a cpp file
+# and a library target works.
+#
+@bjam_signature((["sources", "*"], ["requirements", "*"], ["target_name", "?"]))
+def compile(sources, requirements, target_name=None):
+ return make_test("compile", sources, requirements, target_name)
+
+@bjam_signature((["sources", "*"], ["requirements", "*"], ["target_name", "?"]))
+def compile_fail(sources, requirements, target_name=None):
+ return make_test("compile-fail", sources, requirements, target_name)
+
+@bjam_signature((["sources", "*"], ["requirements", "*"], ["target_name", "?"]))
+def link(sources, requirements, target_name=None):
+ return make_test("link", sources, requirements, target_name)
+
+@bjam_signature((["sources", "*"], ["requirements", "*"], ["target_name", "?"]))
+def link_fail(sources, requirements, target_name=None):
+ return make_test("link-fail", sources, requirements, target_name)
+
+def handle_input_files(input_files):
+ if len(input_files) > 1:
+ # Check that sorting made when creating property-set instance will not
+ # change the ordering.
+ if sorted(input_files) != input_files:
+ get_manager().errors()("Names of input files must be sorted alphabetically\n" +
+ "due to internal limitations")
+ return ["<testing.input-file>" + f for f in input_files]
+
+@bjam_signature((["sources", "*"], ["args", "*"], ["input_files", "*"],
+ ["requirements", "*"], ["target_name", "?"],
+ ["default_build", "*"]))
+def run(sources, args, input_files, requirements, target_name=None, default_build=[]):
+ if args:
+ requirements.append("<testing.arg>" + " ".join(args))
+ requirements.extend(handle_input_files(input_files))
+ return make_test("run", sources, requirements, target_name)
+
+@bjam_signature((["sources", "*"], ["args", "*"], ["input_files", "*"],
+ ["requirements", "*"], ["target_name", "?"],
+ ["default_build", "*"]))
+def run_fail(sources, args, input_files, requirements, target_name=None, default_build=[]):
+ if args:
+ requirements.append("<testing.arg>" + " ".join(args))
+ requirements.extend(handle_input_files(input_files))
+ return make_test("run-fail", sources, requirements, target_name)
+
+# Register all the rules
+for name in ["compile", "compile-fail", "link", "link-fail", "run", "run-fail"]:
+ get_manager().projects().add_rule(name, getattr(sys.modules[__name__], name.replace("-", "_")))
+
+# Use 'test-suite' as a synonym for 'alias', for backward compatibility.
+from b2.build.alias import alias
+get_manager().projects().add_rule("test-suite", alias)
+
+# For all main targets in 'project-module', which are typed targets with type
+# derived from 'TEST', produce some interesting information.
+#
+def dump_tests():
+ for t in __all_tests:
+ dump_test(t)
+
+# Given a project location in normalized form (slashes are forward), compute the
+# name of the Boost library.
+#
+__ln1 = re.compile("/(tools|libs)/(.*)/(test|example)")
+__ln2 = re.compile("/(tools|libs)/(.*)$")
+__ln3 = re.compile("(/status$)")
+def get_library_name(path):
+
+ path = path.replace("\\", "/")
+ match1 = __ln1.match(path)
+ match2 = __ln2.match(path)
+ match3 = __ln3.match(path)
+
+ if match1:
+ return match1.group(2)
+ elif match2:
+ return match2.group(2)
+ elif match3:
+ return ""
+ elif option.get("dump-tests", False, True):
+ # The 'run' rule and others might be used outside boost. In that case,
+ # just return the path, since the 'library name' makes no sense.
+ return path
+
+# Was an XML dump requested?
+__out_xml = option.get("out-xml", False, True)
+
+# Takes a target (instance of 'basic-target') and prints
+# - its type
+# - its name
+# - comments specified via the <test-info> property
+# - relative location of all source from the project root.
+#
+def dump_test(target):
+ type = target.type()
+ name = target.name()
+ project = target.project()
+
+ project_root = project.get('project-root')
+ library = get_library_name(os.path.abspath(project.get('location')))
+ if library:
+ name = library + "/" + name
+
+ sources = target.sources()
+ source_files = []
+ for s in sources:
+ if isinstance(s, targets.FileReference):
+ location = os.path.abspath(os.path.join(s.location(), s.name()))
+ source_files.append(os.path.relpath(location, os.path.abspath(project_root)))
+
+ target_name = project.get('location') + "//" + target.name() + ".test"
+
+ test_info = target.requirements().get('test-info')
+ test_info = " ".join('"' + ti + '"' for ti in test_info)
+
+ # If the user requested XML output on the command-line, add the test info to
+ # that XML file rather than dumping them to stdout.
+ #if $(.out-xml)
+ #{
+# local nl = "
+#" ;
+# .contents on $(.out-xml) +=
+# "$(nl) <test type=\"$(type)\" name=\"$(name)\">"
+# "$(nl) <target><![CDATA[$(target-name)]]></target>"
+# "$(nl) <info><![CDATA[$(test-info)]]></info>"
+# "$(nl) <source><![CDATA[$(source-files)]]></source>"
+# "$(nl) </test>"
+# ;
+# }
+# else
+
+ source_files = " ".join('"' + s + '"' for s in source_files)
+ if test_info:
+ print 'boost-test(%s) "%s" [%s] : %s' % (type, name, test_info, source_files)
+ else:
+ print 'boost-test(%s) "%s" : %s' % (type, name, 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"])
+generators.register_standard("testing.expect-success", ["EXE"], ["LINK"])
+generators.register_standard("testing.expect-failure", ["EXE"], ["LINK_FAIL"])
+
+# Generator which runs an EXE and captures output.
+generators.register_standard("testing.capture-output", ["EXE"], ["RUN_OUTPUT"])
+
+# Generator which creates a target if sources run successfully. Differs from RUN
+# in that run output is not captured. The reason why it exists is that the 'run'
+# rule is much better for automated testing, but is not user-friendly (see
+# http://article.gmane.org/gmane.comp.lib.boost.build/6353).
+generators.register_standard("testing.unit-test", ["EXE"], ["UNIT_TEST"])
+
+# FIXME: if those calls are after bjam.call, then bjam will crash
+# when toolset.flags calls bjam.caller.
+toolset.flags("testing.capture-output", "ARGS", [], ["<testing.arg>"])
+toolset.flags("testing.capture-output", "INPUT_FILES", [], ["<testing.input-file>"])
+toolset.flags("testing.capture-output", "LAUNCHER", [], ["<testing.launcher>"])
+
+toolset.flags("testing.unit-test", "LAUNCHER", [], ["<testing.launcher>"])
+toolset.flags("testing.unit-test", "ARGS", [], ["<testing.arg>"])
+
+type.register("TIME", ["time"])
+generators.register_standard("testing.time", [], ["TIME"])
+
+
+# The following code sets up actions for this module. It's pretty convoluted,
+# but the basic points is that we most of actions are defined by Jam code
+# contained in testing-aux.jam, which we load into Jam module named 'testing'
+
+def run_path_setup(target, sources, ps):
+
+ # For testing, we need to make sure that all dynamic libraries needed by the
+ # test are found. So, we collect all paths from dependency libraries (via
+ # xdll-path property) and add whatever explicit dll-path user has specified.
+ # The resulting paths are added to the environment on each test invocation.
+ dll_paths = ps.get('dll-path')
+ dll_paths.extend(ps.get('xdll-path'))
+ dll_paths.extend(bjam.call("get-target-variable", sources, "RUN_PATH"))
+ dll_paths = unique(dll_paths)
+ if dll_paths:
+ bjam.call("set-target-variable", target, common.prepend_path_variable_command(
+ common.shared_library_path_variable(), dll_paths))
+
+def capture_output_setup(target, sources, ps):
+ run_path_setup(target, sources, ps)
+
+ if ps.get('preserve-test-targets') == ['off']:
+ bjam.call("set-target-variable", target, "REMOVE_TEST_TARGETS", "1")
+
+get_manager().engine().register_bjam_action("testing.capture-output",
+ capture_output_setup)
+
+
+path = os.path.dirname(get_manager().projects().loaded_tool_module_path_[__name__])
+import b2.util.os_j
+get_manager().projects().project_rules()._import_rule("testing", "os.name",
+ b2.util.os_j.name)
+import b2.tools.common
+get_manager().projects().project_rules()._import_rule("testing", "common.rm-command",
+ b2.tools.common.rm_command)
+get_manager().projects().project_rules()._import_rule("testing", "common.file-creation-command",
+ b2.tools.common.file_creation_command)
+
+bjam.call("load", "testing", os.path.join(path, "testing-aux.jam"))
+
+
+for name in ["expect-success", "expect-failure", "unit-test", "time"]:
+ get_manager().engine().register_bjam_action("testing." + name)
+
+
+if option.get("dump-tests", False, True):
+ build_system.add_pre_build_hook(dump_tests)

Modified: trunk/tools/build/v2/util/__init__.py
==============================================================================
--- trunk/tools/build/v2/util/__init__.py (original)
+++ trunk/tools/build/v2/util/__init__.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -48,3 +48,10 @@
         args = ("set-update-action", name) + args
 
     return bjam.call(*args)
+
+def stem(filename):
+ i = filename.find('.')
+ if i != -1:
+ return filename[0:i]
+ else:
+ return filename

Modified: trunk/tools/build/v2/util/option.py
==============================================================================
--- trunk/tools/build/v2/util/option.py (original)
+++ trunk/tools/build/v2/util/option.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -26,7 +26,7 @@
     if matches:
         return matches[-1]
     else:
- m = b2.util.regex.transform(sys.argv, "--" + re.escape(name))
+ m = b2.util.regex.transform(sys.argv, "--(" + re.escape(name) + ")")
         if m and implied_value:
             return implied_value
         elif options.has_key(name):

Modified: trunk/tools/build/v2/util/os_j.py
==============================================================================
--- trunk/tools/build/v2/util/os_j.py (original)
+++ trunk/tools/build/v2/util/os_j.py 2010-08-04 06:16:08 EDT (Wed, 04 Aug 2010)
@@ -9,8 +9,11 @@
 # 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)
 
+import bjam
 
-import os
+__OS = bjam.call("peek", [], "OS")[0]
 
+# Return Jam's name of OS to prevent existing code from burning
+# when faced with Python naming
 def name():
- return os.name
+ return __OS


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk