Command() and Scanner test coverage. Improved test infrastructure
for -c output.
+ - Refactor the interface between Action and Executor objects to treat
+ Actions atomically.
+
+ - The --debug=presub option will now report the pre-substitution
+ each action seprately, instead of reporting the entire list before
+ executing the actions one by one.
+
+ - The --debug=explain option explaining a changed action will now
+ (more correctly) show pre-substitution action strings, instead of
+ the commands with substituted file names.
+
+ - A Node (file) will now be rebuilt if its PreAction or PostAction
+ actions change.
+
+ - Python Function actions now have their calling signature (target,
+ source, env) reported correctly when displayed.
+
From Levi Stephen:
- Allow $JARCHDIR to be expanded to other construction variables.
OO utility methods and some generic methods for displaying information
about an Action in response to the various commands that control printing.
+A second-level base class is _ActionAction. This extends ActionBase
+by providing the methods that can be used to show and perform an
+action. True Action objects will subclass _ActionAction; Action
+factory class objects will subclass ActionBase.
+
The heavy lifting is handled by subclasses for the different types of
actions we might execute:
needs to be rebuilt because its action changed.
genstring()
- Returns a string representation of the Action *without* command
- substitution, but allows a CommandGeneratorAction to generate
- the right action based on the specified target, source and env.
- This is used by the Signature subsystem (through the Executor)
- to compare the actions used to build a target last time and
- this time.
+ Returns a string representation of the Action *without*
+ command substitution, but allows a CommandGeneratorAction to
+ generate the right action based on the specified target,
+ source and env. This is used by the Signature subsystem
+ (through the Executor) to obtain an (imprecise) representation
+ of the Action operation for informative purposes.
- strfunction()
- Returns a substituted string representation of the Action.
- This is used by the ActionBase.show() command to display the
- command/function that will be executed to generate the target(s).
Subclasses also supply the following methods for internal use within
this module:
__str__()
- Returns a string representation of the Action *without* command
- substitution. This is used by the __call__() methods to display
- the pre-substitution command whenever the --debug=presub option
- is used.
-
+ Returns a string approximation of the Action; no variable
+ substitution is performed.
+
execute()
The internal method that really, truly, actually handles the
execution of a command or Python function. This is used so
pre-substitution representations, and *then* execute an action
without worrying about the specific Actions involved.
+ strfunction()
+ Returns a substituted string representation of the Action.
+ This is used by the _ActionAction.show() command to display the
+ command/function that will be executed to generate the target(s).
+
There is a related independent ActionCaller class that looks like a
regular Action, and which serves as a wrapper for arbitrary functions
that we want to let the user specify the arguments to now, but actually
if len(commands) == 1:
return apply(CommandAction, (commands[0],)+args, kw)
else:
- listCmdActions = map(lambda x: CommandAction(x), commands)
- return apply(ListAction, (listCmdActions,)+args, kw)
+ listCmdActions = map(lambda x, args=args, kw=kw:
+ apply(CommandAction, (x,)+args, kw),
+ commands)
+ return ListAction(listCmdActions)
return None
def Action(act, *args, **kw):
if len(acts) == 1:
return acts[0]
else:
- return apply(ListAction, (acts,)+args, kw)
+ return ListAction(acts)
else:
return apply(_do_create_action, (act,)+args, kw)
class ActionBase:
+ """Base class for all types of action objects that can be held by
+ other objects (Builders, Executors, etc.) This provides the
+ common methods for manipulating and combining those actions."""
+
+ def __cmp__(self, other):
+ return cmp(self.__dict__, other)
+
+ def genstring(self, target, source, env):
+ return str(self)
+
+ def __add__(self, other):
+ return _actionAppend(self, other)
+
+ def __radd__(self, other):
+ return _actionAppend(other, self)
+
+ def presub_lines(self, env):
+ # CommandGeneratorAction needs a real environment
+ # in order to return the proper string here, since
+ # it may call LazyCmdGenerator, which looks up a key
+ # in that env. So we temporarily remember the env here,
+ # and CommandGeneratorAction will use this env
+ # when it calls its __generate method.
+ self.presub_env = env
+ lines = string.split(str(self), '\n')
+ self.presub_env = None # don't need this any more
+ return lines
+
+
+class _ActionAction(ActionBase):
"""Base class for actions that create output objects."""
def __init__(self, strfunction=_null, presub=_null, chdir=None, **kw):
if not strfunction is _null:
self.presub = presub
self.chdir = chdir
- def __cmp__(self, other):
- return cmp(self.__dict__, other)
-
def print_cmd_line(self, s, target, source, env):
sys.stdout.write(s + "\n")
if not SCons.Util.is_String(chdir):
chdir = str(target[0].dir)
if presub:
- t = string.join(map(str, target), 'and')
+ t = string.join(map(str, target), ' and ')
l = string.join(self.presub_lines(env), '\n ')
- out = "Building %s with action(s):\n %s\n" % (t, l)
+ out = "Building %s with action:\n %s\n" % (t, l)
sys.stdout.write(out)
s = None
if show and self.strfunction:
print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
return stat
- def presub_lines(self, env):
- # CommandGeneratorAction needs a real environment
- # in order to return the proper string here, since
- # it may call LazyCmdGenerator, which looks up a key
- # in that env. So we temporarily remember the env here,
- # and CommandGeneratorAction will use this env
- # when it calls its __generate method.
- self.presub_env = env
- lines = string.split(str(self), '\n')
- self.presub_env = None # don't need this any more
- return lines
-
- def genstring(self, target, source, env):
- return str(self)
-
- def __add__(self, other):
- return _actionAppend(self, other)
-
- def __radd__(self, other):
- return _actionAppend(other, self)
def _string_from_cmd_list(cmd_list):
"""Takes a list of command line arguments and returns a pretty
cl.append(arg)
return string.join(cl)
-class CommandAction(ActionBase):
+class CommandAction(_ActionAction):
"""Class for command-execution actions."""
def __init__(self, cmd, *args, **kw):
- # Cmd list can actually be a list or a single item...basically
- # anything that we could pass in as the first arg to
- # Environment.subst_list().
+ # Cmd can actually be a list or a single item; if it's a
+ # single item it should be the command string to execute; if a
+ # list then it should be the words of the command string to
+ # execute. Only a single command should be executed by this
+ # object; lists of commands should be handled by embedding
+ # these objects in a ListAction object (which the Action()
+ # factory above does). cmd will be passed to
+ # Environment.subst_list() for substituting environment
+ # variables.
if __debug__: logInstanceCreation(self)
- apply(ActionBase.__init__, (self,)+args, kw)
+ apply(_ActionAction.__init__, (self,)+args, kw)
+ if SCons.Util.is_List(cmd):
+ if filter(SCons.Util.is_List, cmd):
+ raise TypeError, "CommandAction should be given only " \
+ "a single command"
self.cmd_list = cmd
def __str__(self):
+ if SCons.Util.is_List(self.cmd_list):
+ return string.join(map(str, self.cmd_list), ' ')
return str(self.cmd_list)
def strfunction(self, target, source, env):
cmd_list = env.subst_list(self.cmd_list, 0, target, source)
- return string.join(map(_string_from_cmd_list, cmd_list), "\n")
+ return _string_from_cmd_list(cmd_list[0])
def execute(self, target, source, env):
"""Execute a command action.
# reasonable for just about everything else:
ENV[key] = str(value)
- cmd_list = env.subst_list(self.cmd_list, 0, target, source)
+ cmd_list = env.subst_list(self.cmd_list, 0, target,
+ map(rfile, source))
# Use len() to filter out any "command" that's zero-length.
for cmd_line in filter(len, cmd_list):
"""Class for command-generator actions."""
def __init__(self, generator, *args, **kw):
if __debug__: logInstanceCreation(self)
- apply(ActionBase.__init__, (self,)+args, kw)
self.generator = generator
+ self.gen_kw = kw
def __generate(self, target, source, env, for_signature):
# ensure that target is a list, to make it easier to write
target = [target]
ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
- gen_cmd = Action(ret)
+ gen_cmd = apply(Action, (ret,), self.gen_kw)
if not gen_cmd:
raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
return gen_cmd
- def strfunction(self, target, source, env):
- if not SCons.Util.is_List(source):
- source = [source]
- rsources = map(rfile, source)
- act = self.__generate(target, source, env, 0)
- if act.strfunction:
- return act.strfunction(target, rsources, env)
- else:
- return None
-
def __str__(self):
try:
env = self.presub_env or {}
except AttributeError:
env = {}
- act = self.__generate([], [], env, 0)
+ act = self.__generate([], [], env, 1)
return str(act)
def genstring(self, target, source, env):
- return str(self.__generate(target, source, env, 0))
+ return self.__generate(target, source, env, 1).genstring(target, source, env)
- def execute(self, target, source, env):
- rsources = map(rfile, source)
+ def __call__(self, target, source, env, errfunc=None, presub=_null,
+ show=_null, execute=_null, chdir=_null):
act = self.__generate(target, source, env, 0)
- return act.execute(target, source, env)
+ return act(target, source, env, errfunc, presub,
+ show, execute, chdir)
def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this action's command line.
if __debug__: logInstanceCreation(self)
self.var = SCons.Util.to_String(var)
- def strfunction(self, target, source, env):
- try:
- return env[self.var]
- except KeyError:
- # The variable reference substitutes to nothing.
- return ''
-
def __str__(self):
return 'LazyCmdGenerator: %s'%str(self.var)
def __cmp__(self, other):
return cmp(self.__dict__, other)
-class FunctionAction(ActionBase):
+class FunctionAction(_ActionAction):
"""Class for Python function actions."""
def __init__(self, execfunction, *args, **kw):
if __debug__: logInstanceCreation(self)
self.execfunction = execfunction
- apply(ActionBase.__init__, (self,)+args, kw)
+ apply(_ActionAction.__init__, (self,)+args, kw)
self.varlist = kw.get('varlist', [])
def function_name(self):
return "%s(%s, %s)" % (name, tstr, sstr)
def __str__(self):
- return "%s(env, target, source)" % self.function_name()
+ return "%s(target, source, env)" % self.function_name()
def execute(self, target, source, env):
rsources = map(rfile, source)
class ListAction(ActionBase):
"""Class for lists of other actions."""
- def __init__(self, list, *args, **kw):
+ def __init__(self, list):
if __debug__: logInstanceCreation(self)
- apply(ActionBase.__init__, (self,)+args, kw)
- self.list = map(lambda x: Action(x), list)
+ def list_of_actions(x):
+ if isinstance(x, ActionBase):
+ return x
+ return Action(x)
+ self.list = map(list_of_actions, list)
def __str__(self):
- s = []
- for l in self.list:
- s.append(str(l))
- return string.join(s, "\n")
-
- def strfunction(self, target, source, env):
- s = []
- for l in self.list:
- if l.strfunction:
- x = l.strfunction(target, source, env)
- if not SCons.Util.is_List(x):
- x = [x]
- s.extend(x)
- return string.join(s, "\n")
-
- def execute(self, target, source, env):
- for l in self.list:
- r = l.execute(target, source, env)
- if r:
- return r
- return 0
+ return string.join(map(str, self.list), '\n')
+
+ def presub_lines(self, env):
+ return SCons.Util.flatten(map(lambda a, env=env:
+ a.presub_lines(env),
+ self.list))
def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this action list.
self.list),
"")
+ def __call__(self, target, source, env, errfunc=None, presub=_null,
+ show=_null, execute=_null, chdir=_null):
+ for act in self.list:
+ stat = act(target, source, env, errfunc, presub,
+ show, execute, chdir)
+ if stat:
+ return stat
+ return 0
+
class ActionCaller:
"""A class for delaying calling an Action function with specific
(positional and keyword) arguments until the Action is actually
self.strfunc = strfunc
def __call__(self, *args, **kw):
ac = ActionCaller(self, args, kw)
- return Action(ac, strfunction=ac.strfunction)
+ action = Action(ac, strfunction=ac.strfunction)
+ # action will be a FunctionAction; if left to its own devices,
+ # a genstr or str of this action will just show
+ # "ActionCaller(target, source, env)". Override that with the
+ # description from strfunc. Note that the apply is evaluated
+ # right now; __str__ is set to a (lambda) function that just
+ # returns the stored result of the evaluation whenever called.
+ action.__str__ = lambda name=apply(self.strfunc, args, kw): name
+ return action
assert isinstance(a4, SCons.Action.ListAction), a4
assert isinstance(a4.list[0], SCons.Action.CommandAction), a4.list[0]
assert a4.list[0].cmd_list == "x", a4.list[0].cmd_list
+ assert a4.list[0].strfunction == foo, a4.list[0].strfunction
assert isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1]
assert a4.list[1].cmd_list == "y", a4.list[1].cmd_list
- assert a4.strfunction == foo, a4.strfunction
+ assert a4.list[1].strfunction == foo, a4.list[1].strfunction
a5 = SCons.Action.Action("x\ny", strfunction=foo)
assert isinstance(a5, SCons.Action.ListAction), a5
assert isinstance(a5.list[0], SCons.Action.CommandAction), a5.list[0]
assert a5.list[0].cmd_list == "x", a5.list[0].cmd_list
+ assert a5.list[0].strfunction == foo, a5.list[0].strfunction
assert isinstance(a5.list[1], SCons.Action.CommandAction), a5.list[1]
assert a5.list[1].cmd_list == "y", a5.list[1].cmd_list
- assert a5.strfunction == foo, a5.strfunction
+ assert a5.list[1].strfunction == foo, a5.list[1].strfunction
def test_CommandGeneratorAction(self):
"""Test the Action() factory's creation of CommandGeneratorAction objects
a2 = SCons.Action.Action(cg, strfunction=bar)
assert isinstance(a2, SCons.Action.CommandGeneratorAction), a2
assert a2.generator is foo, a2.generator
- assert a2.strfunction is bar, a2.strfunction
def test_LazyCmdGeneratorAction(self):
"""Test the Action() factory's creation of lazy CommandGeneratorAction objects
a2 = SCons.Action.Action("$FOO", strfunction=foo)
assert isinstance(a2, SCons.Action.CommandGeneratorAction), a2
assert isinstance(a2.generator, SCons.Action.LazyCmdGenerator), a2.generator
- assert a2.strfunction is foo, a2.strfunction
def test_no_action(self):
"""Test when the Action() factory can't create an action object
assert a2 is a1, a2
class ActionBaseTestCase(unittest.TestCase):
+ # Maybe write this in the future...
+ pass
+
+class _ActionActionTestCase(unittest.TestCase):
+
def test__init__(self):
- """Test creation of ActionBase objects
+ """Test creation of _ActionAction objects
"""
def func1():
def func2():
pass
- a = SCons.Action.ActionBase()
+ a = SCons.Action._ActionAction()
assert not hasattr(a, 'strfunction')
- assert SCons.Action.ActionBase(kwarg = 1)
+ assert SCons.Action._ActionAction(kwarg = 1)
assert not hasattr(a, 'strfunction')
assert not hasattr(a, 'kwarg')
- a = SCons.Action.ActionBase(strfunction=func1)
+ a = SCons.Action._ActionAction(strfunction=func1)
assert a.strfunction is func1, a.strfunction
- a = SCons.Action.ActionBase(presub=func1)
+ a = SCons.Action._ActionAction(presub=func1)
assert a.presub is func1, a.presub
- a = SCons.Action.ActionBase(chdir=1)
+ a = SCons.Action._ActionAction(chdir=1)
assert a.chdir is 1, a.chdir
- a = SCons.Action.ActionBase(func1, func2, 'x')
+ a = SCons.Action._ActionAction(func1, func2, 'x')
assert a.strfunction is func1, a.strfunction
assert a.presub is func2, a.presub
assert a.chdir is 'x', a.chdir
return 7
a = SCons.Action.Action(execfunc)
+ def firstfunc(target, source, env):
+ assert type(target) is type([]), type(target)
+ assert type(source) is type([]), type(source)
+ return 0
+ def lastfunc(target, source, env):
+ assert type(target) is type([]), type(target)
+ assert type(source) is type([]), type(source)
+ return 9
+ b = SCons.Action.Action([firstfunc, execfunc, lastfunc])
+
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env)
a.chdir = None
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ result = b("out", "in", env)
+ assert result == 7, result
+ s = sio.getvalue()
+ assert s == 'firstfunc(["out"], ["in"])\nexecfunc(["out"], ["in"])\n', s
+
SCons.Action.execute_actions = 0
sio = StringIO.StringIO()
s = sio.getvalue()
assert s == 'execfunc(["out"], ["in"])\n', s
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ result = b("out", "in", env)
+ assert result == 0, result
+ s = sio.getvalue()
+ assert s == 'firstfunc(["out"], ["in"])\nexecfunc(["out"], ["in"])\nlastfunc(["out"], ["in"])\n', s
+
SCons.Action.print_actions_presub = 1
+ SCons.Action.execute_actions = 1
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env)
- assert result == 0, result
+ assert result == 7, result
s = sio.getvalue()
assert s == 'execfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env, presub=1)
- assert result == 0, result
+ assert result == 7, result
+ s = sio.getvalue()
+ assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
+
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ result = b(["out"], "in", env, presub=1)
+ assert result == 7, result
+ s = sio.getvalue()
+ assert s == 'Building out with action:\n firstfunc(target, source, env)\nfirstfunc(["out"], ["in"])\nBuilding out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
+
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ result = b(["out", "list"], "in", env, presub=1)
+ assert result == 7, result
s = sio.getvalue()
- assert s == 'Building out with action(s):\n execfunc(env, target, source)\nexecfunc(["out"], ["in"])\n', s
+ assert s == 'Building out and list with action:\n firstfunc(target, source, env)\nfirstfunc(["out", "list"], ["in"])\nBuilding out and list with action:\n execfunc(target, source, env)\nexecfunc(["out", "list"], ["in"])\n', s
a2 = SCons.Action.Action(execfunc)
sio = StringIO.StringIO()
sys.stdout = sio
result = a2("out", "in", env)
- assert result == 0, result
+ assert result == 7, result
s = sio.getvalue()
- assert s == 'Building out with action(s):\n execfunc(env, target, source)\nexecfunc(["out"], ["in"])\n', s
+ assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
+
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ result = a2("out", "in", env, presub=0)
+ assert result == 7, result
+ s = sio.getvalue()
+ assert s == 'execfunc(["out"], ["in"])\n', s
+
+ SCons.Action.execute_actions = 0
sio = StringIO.StringIO()
sys.stdout = sio
assert result == 7, result
assert errfunc_result == [7], errfunc_result
+ SCons.Action.execute_actions = 1
+
result = []
def my_print_cmd_line(s, target, source, env, result=result):
result.append(s)
pass
a = SCons.Action.Action(func)
s = a.presub_lines(env)
- assert s == ["func(env, target, source)"], s
+ assert s == ["func(target, source, env)"], s
def gen(target, source, env, for_signature):
return 'generat' + env.get('GEN', 'or')
'$TARGET', '$SOURCE',
'$TARGETS', '$SOURCES'])
s = str(act)
- assert s == "['xyzzy', '$TARGET', '$SOURCE', '$TARGETS', '$SOURCES']", s
+ assert s == "xyzzy $TARGET $SOURCE $TARGETS $SOURCES", s
def test_genstring(self):
"""Test the genstring() method for command Actions
act = SCons.Action.CommandAction(['xyzzy',
'$TARGET', '$SOURCE',
'$TARGETS', '$SOURCES'])
- expect = "['xyzzy', '$TARGET', '$SOURCE', '$TARGETS', '$SOURCES']"
+ expect = "xyzzy $TARGET $SOURCE $TARGETS $SOURCES"
s = act.genstring([], [], env)
assert s == expect, s
s = act.genstring([t1], [s1], env)
assert self.dummy == 1, self.dummy
assert s == "$FOO $TARGET $SOURCE $TARGETS $SOURCES", s
- def test_strfunction(self):
- """Test the command generator Action string function
- """
- def f(target, source, env, for_signature, self=self):
- dummy = env['dummy']
- self.dummy = dummy
- return "$FOO"
- a = SCons.Action.CommandGeneratorAction(f)
- self.dummy = 0
- s = a.strfunction([], [], env=Environment(FOO='xyzzy', dummy=1))
- assert self.dummy == 1, self.dummy
- assert s == 'xyzzy', s
-
- def sf(target, source, env):
- return "sf was called"
- a = SCons.Action.CommandGeneratorAction(f, strfunction=sf)
- s = a.strfunction([], [], env=Environment())
- assert s == "sf was called", s
-
- def f(target, source, env, for_signature, self=self):
- def null(target, source, env):
- pass
- return SCons.Action.Action(null, strfunction=None)
- a = SCons.Action.CommandGeneratorAction(f)
- s = a.strfunction([], [], env=Environment())
-
def test_execute(self):
"""Test executing a command generator Action
"""
pass
a = SCons.Action.FunctionAction(func1)
s = str(a)
- assert s == "func1(env, target, source)", s
+ assert s == "func1(target, source, env)", s
class class1:
def __call__(self):
pass
a = SCons.Action.FunctionAction(class1())
s = str(a)
- assert s == "class1(env, target, source)", s
+ assert s == "class1(target, source, env)", s
def test_execute(self):
"""Test executing a function Action
pass
a = SCons.Action.ListAction([f, g, "XXX", f])
s = str(a)
- assert s == "f(env, target, source)\ng(env, target, source)\nXXX\nf(env, target, source)", s
+ assert s == "f(target, source, env)\ng(target, source, env)\nXXX\nf(target, source, env)", s
def test_genstring(self):
"""Test the genstring() method for a list of subsidiary Actions
pass
a = SCons.Action.ListAction([f, g, "XXX", f])
s = a.genstring([], [], Environment())
- assert s == "f(env, target, source)\ng(env, target, source)\nXXX\nf(env, target, source)", s
-
- def test_strfunction(self):
- """Test the string function for a list of subsidiary Actions
- """
- def f(target,source,env):
- pass
- def g(target,source,env):
- pass
- a = SCons.Action.ListAction([f, g, "XXX", f])
- s = a.strfunction([], [], Environment())
- assert s == "f([], [])\ng([], [])\nXXX\nf([], [])", s
-
- def sf(target, source, env):
- return "sf was called"
- act = SCons.Action.ListAction([f, g, "XXX", f], strfunction=sf)
- s = act.strfunction([], [], Environment())
- assert s == "sf was called", s
+ assert s == "f(target, source, env)\ng(target, source, env)\nXXX\nf(target, source, env)", s
def test_execute(self):
"""Test executing a list of subsidiary Actions
assert isinstance(a9, SCons.Action.CommandGeneratorAction), a10
assert a10.generator.var == 'FOO', a10.generator.var
- def test_strfunction(self):
- """Test the lazy-evaluation Action string function
- """
- def f(target, source, env):
- pass
- a = SCons.Action.Action('$BAR')
- s = a.strfunction([], [], env=Environment(BAR=f, s=self))
- assert s == "f([], [])", s
-
def test_genstring(self):
"""Test the lazy-evaluation Action genstring() method
"""
pass
a = SCons.Action.Action('$BAR')
s = a.genstring([], [], env=Environment(BAR=f, s=self))
- assert s == "f(env, target, source)", s
+ assert s == "f(target, source, env)", s
def test_execute(self):
"""Test executing a lazy-evaluation Action
af = SCons.Action.ActionFactory(actfunc, strfunc)
af(3, 6, 9)([], [], Environment())
assert actfunc_args == [3, 6, 9], actfunc_args
- assert strfunc_args == [3, 6, 9], strfunc_args
+ # Note that strfunc gets evaluated twice: once when we called
+ # the actionfactory itself to get the real action
+ # (Action(ActionCaller, ...)), and once when we actually call
+ # that resulting action; since strfunc modifies the global,
+ # account for the number of times it was called.
+ assert strfunc_args == [3, 6, 9, 3, 6, 9], strfunc_args
class ActionCompareTestCase(unittest.TestCase):
suite = unittest.TestSuite()
tclasses = [ ActionTestCase,
ActionBaseTestCase,
+ _ActionActionTestCase,
CommandActionTestCase,
CommandGeneratorActionTestCase,
FunctionActionTestCase,
if t_contents == contents:
SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning,
- "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s"%(str(t), t.builder.action.strfunction(tlist, slist, t.env)))
+ "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s"%(str(t), t.builder.action.genstring(tlist, slist, t.env)))
else:
raise UserError, "Two environments with different actions were specified for the same target: %s"%str(t)
self.sources))
return self.build_env
- def get_action_list(self, target):
- """Fetch or create the appropriate action list (for this target).
-
- There is an architectural mistake here: we cache the action list
- for the Executor and re-use it regardless of which target is
- being asked for. In practice, this doesn't seem to be a problem
- because executing the action list will update all of the targets
- involved, so only one target's pre- and post-actions will win,
- anyway. This is probably a bug we should fix...
- """
- al = [self.action]
- try:
- # XXX shouldn't reach into node attributes like this
- return target.pre_actions + al + target.post_actions
- except AttributeError:
- return al
-
def do_nothing(self, target, errfunc, **kw):
pass
def __call__(self, target, errfunc, **kw):
"""Actually execute the action list."""
- action_list = self.get_action_list(target)
- if not action_list:
- return
- env = self.get_build_env()
kw = kw.copy()
kw.update(self.builder_kw)
- for action in action_list:
- apply(action, (self.targets, self.sources, env, errfunc), kw)
+ apply(self.action, (self.targets, self.sources,
+ self.get_build_env(), errfunc), kw)
def cleanup(self):
try:
self.get_build_env())
return self.string
- def strfunction(self):
- try:
- return self._strfunc
- except AttributeError:
- action = self.action
- build_env = self.get_build_env()
- if action.strfunction is None:
- # This instance has strfunction set to None to suppress
- # printing of the action. Call the method directly
- # through the class instead.
- self._strfunc = action.__class__.strfunction(action,
- self.targets,
- self.sources,
- build_env)
- else:
- self._strfunc = action.strfunction(self.targets,
- self.sources,
- build_env)
- return self._strfunc
-
def nullify(self):
self.__call__ = self.do_nothing
self.string = ''
- self._strfunc = None
-
- def get_raw_contents(self):
- """Fetch the raw signature contents. This, along with
- get_contents(), is the real reason this class exists, so we can
- compute this once and cache it regardless of how many target or
- source Nodes there are.
- """
- try:
- return self.raw_contents
- except AttributeError:
- action = self.action
- self.raw_contents = action.get_raw_contents(self.targets,
- self.sources,
- self.get_build_env())
- return self.raw_contents
def get_contents(self):
"""Fetch the signature contents. This, along with
self.actions = actions
def __call__(self, target, source, env, errfunc, **kw):
for action in self.actions:
- action(target, source, env, errfunc)
- def strfunction(self, target, source, env):
- return string.join(['STRFUNCTION'] + map(str, self.actions) + target + source)
+ apply(action, (target, source, env, errfunc), kw)
def genstring(self, target, source, env):
return string.join(['GENSTRING'] + map(str, self.actions) + target + source)
- def get_raw_contents(self, target, source, env):
- return string.join(['RAW'] + self.actions + target + source)
def get_contents(self, target, source, env):
return string.join(self.actions + target + source)
def __init__(self, pre, post):
self.pre_actions = pre
self.post_actions = post
+ def build(self, errfunc=None):
+ executor = SCons.Executor.Executor(MyAction(self.pre_actions +
+ [self.builder.action] +
+ self.post_actions),
+ self.builder.env,
+ [],
+ [self],
+ ['s1', 's2'])
+ apply(executor, (self, errfunc), {})
class ExecutorTestCase(unittest.TestCase):
assert be['O'] == 'ob3', be['O']
assert be['Y'] == 'yyy', be['Y']
- def test_get_action_list(self):
- """Test fetching and generating an action list"""
- x = SCons.Executor.Executor('b', 'e', 'o', 't', 's')
- al = x.get_action_list(MyNode([], []))
- assert al == ['b'], al
- al = x.get_action_list(MyNode(['PRE'], ['POST']))
- assert al == ['PRE', 'b', 'POST'], al
-
- a = MyAction()
- x = SCons.Executor.Executor(a, None, {}, 't', 's')
- al = x.get_action_list(MyNode(['pre'], ['post']))
- assert al == ['pre', a, 'post'], al
-
def test__call__(self):
"""Test calling an Executor"""
result = []
env = MyEnvironment()
a = MyAction([action1, action2])
- x = SCons.Executor.Executor(a, env, [], ['t1', 't2'], ['s1', 's2'])
-
- x(MyNode([pre], [post]), None)
+ b = MyBuilder(env, {})
+ b.action = a
+ n = MyNode([pre], [post])
+ n.builder = b
+ n.build()
assert result == ['pre', 'action1', 'action2', 'post'], result
del result[:]
errfunc(1)
return 1
- x(MyNode([pre_err], [post]), None)
+ n = MyNode([pre_err], [post])
+ n.builder = b
+ n.build()
assert result == ['pre_err', 'action1', 'action2', 'post'], result
del result[:]
raise "errfunc %s" % stat
try:
- x(MyNode([pre_err], [post]), errfunc)
+ n.build(errfunc)
except:
assert sys.exc_type == "errfunc 1", sys.exc_type
else:
c = str(x)
assert c == 'GENSTRING action1 action2 t s', c
- def test_strfunction(self):
- """Test the strfunction() method"""
- env = MyEnvironment(S='string')
-
- x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
- s = x.strfunction()
- assert s == 'STRFUNCTION action1 action2 t s', s
-
def test_nullify(self):
"""Test the nullify() method"""
env = MyEnvironment(S='string')
assert result == ['action1'], result
s = str(x)
assert s[:10] == 'GENSTRING ', s
- s = x.strfunction()
- assert s[:12] == 'STRFUNCTION ', s
del result[:]
x.nullify()
assert result == [], result
s = str(x)
assert s == '', s
- s = x.strfunction()
- assert s == None, s
-
- def test_get_raw_contents(self):
- """Test fetching the raw signatures contents"""
- env = MyEnvironment(RC='raw contents')
-
- x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
- x.raw_contents = 'raw raw raw'
- rc = x.get_raw_contents()
- assert rc == 'raw raw raw', rc
-
- x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
- rc = x.get_raw_contents()
- assert rc == 'RAW action1 action2 t s', rc
def test_get_contents(self):
"""Test fetching the signatures contents"""
c = x.get_contents()
assert c == 'action1 action2 t s', c
- def test_get_timetstamp(self):
+ def test_get_timestamp(self):
"""Test fetching the "timestamp" """
x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
ts = x.get_timestamp()
cycle_detected = None
built_order = 0
-class MyAction:
+def _actionAppend(a1, a2):
+ all = []
+ for curr_a in [a1, a2]:
+ if isinstance(curr_a, MyAction):
+ all.append(curr_a)
+ elif isinstance(curr_a, MyListAction):
+ all.extend(curr_a.list)
+ elif type(curr_a) == type([1,2]):
+ all.extend(curr_a)
+ else:
+ raise 'Cannot Combine Actions'
+ return MyListAction(all)
+
+class MyActionBase:
+ def __add__(self, other):
+ return _actionAppend(self, other)
+
+ def __radd__(self, other):
+ return _actionAppend(other, self)
+
+class MyAction(MyActionBase):
def __init__(self):
self.order = 0
self.order = built_order
return 0
- def get_actions(self):
- return [self]
-
-class MyNonGlobalAction:
+class MyListAction(MyActionBase):
+ def __init__(self, list):
+ self.list = list
+ def __call__(self, target, source, env, errfunc):
+ for A in self.list:
+ A(target, source, env, errfunc)
+
+class MyNonGlobalAction(MyActionBase):
def __init__(self):
self.order = 0
self.built_it = None
self.order = built_order
return 0
- def get_actions(self):
- return [self]
-
class Environment:
def __init__(self, **kw):
self._dict = {}
if not create:
raise
import SCons.Executor
- executor = SCons.Executor.Executor(self.builder.action,
+ act = self.builder.action
+ if self.pre_actions:
+ act = self.pre_actions + act
+ if self.post_actions:
+ act = act + self.post_actions
+ executor = SCons.Executor.Executor(act,
self.builder.env,
[self.builder.overrides],
[self],
self.executor = executor
return executor
+ def reset_executor(self):
+ "Remove cached executor; forces recompute when needed."
+ try:
+ delattr(self, 'executor')
+ except AttributeError:
+ pass
+
def retrieve_from_cache(self):
"""Try to retrieve the node's content from a cache
if self.has_builder():
executor = self.get_executor()
- binfo.bact = executor.strfunction()
+ binfo.bact = str(executor)
binfo.bactsig = calc.module.signature(executor)
sigs.append(binfo.bactsig)
"""Adds an Action performed on this Node only before
building it."""
self.pre_actions.append(act)
+ # executor must be recomputed to include new pre-actions
+ self.reset_executor()
def add_post_action(self, act):
"""Adds and Action performed on this Node only after
building it."""
self.post_actions.append(act)
+ # executor must be recomputed to include new pre-actions
+ self.reset_executor()
def render_include_tree(self):
"""
test.run(arguments = '-Q .', stdout = """\
foo
-func(env, target, source)
+func(target, source, env)
arg1
arg2
scons: `.' is up to date.
os.chdir(%(work2)s)
os.chdir('sub')
%(python)s %(cat_py)s .temp f2.in
+os.chdir(%(work2)s)
+os.chdir('sub')
%(python)s %(cat_py)s f2.out .temp
os.chdir(%(work2)s)
""" % locals())
test.must_match(['work1', 'src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
+#
+test.write(['work1', 'src', 'SConscript'], """\
+Import("env")
+f3 = File('file3')
+env.Cat(f3, ['zzz', 'yyy', 'xxx'])
+env.AddPostAction(f3, r"%(python)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy")
+env.AddPreAction(f3, r"%(python)s %(cat_py)s ${TARGET}.alt $SOURCES")
+""" % {'python':python, 'cat_py':cat_py})
+
+test.run(chdir='work1/src', arguments=args, stdout=test.wrap_stdout("""\
+scons: rebuilding `file3' because the build action changed:
+ old: %(python)s %(cat_py)s $TARGET $SOURCES
+ new: %(python)s %(cat_py)s ${TARGET}.alt $SOURCES
+ %(python)s %(cat_py)s $TARGET $SOURCES
+ %(python)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy
+%(python)s %(cat_py)s file3.alt zzz yyy xxx
+%(python)s %(cat_py)s file3 zzz yyy xxx
+%(python)s %(cat_py)s file3.yyy zzz yyy xxx yyy
+""" % {'python':python, 'cat_py':cat_py}))
+
+test.must_match(['work1', 'src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
+test.must_match(['work1', 'src', 'file3.alt'], "zzz 2\nyyy 2\nxxx 1\n")
+test.must_match(['work1', 'src', 'file3.yyy'], "zzz 2\nyyy 2\nxxx 1\nyyy 2\n")
+
+#
+test.write(['work1', 'src', 'SConscript'], """\
+Import("env")
+f3 = File('file3')
+env.Cat(f3, ['zzz', 'yyy', 'xxx'])
+env.AddPostAction(f3, r"%(python)s %(cat_py)s ${TARGET}.yyy $SOURCES xxx")
+env.AddPreAction(f3, r"%(python)s %(cat_py)s ${TARGET}.alt $SOURCES")
+""" % {'python':python, 'cat_py':cat_py})
+
+test.run(chdir='work1/src', arguments=args, stdout=test.wrap_stdout("""\
+scons: rebuilding `file3' because the build action changed:
+ old: %(python)s %(cat_py)s ${TARGET}.alt $SOURCES
+ %(python)s %(cat_py)s $TARGET $SOURCES
+ %(python)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy
+ new: %(python)s %(cat_py)s ${TARGET}.alt $SOURCES
+ %(python)s %(cat_py)s $TARGET $SOURCES
+ %(python)s %(cat_py)s ${TARGET}.yyy $SOURCES xxx
+%(python)s %(cat_py)s file3.alt zzz yyy xxx
+%(python)s %(cat_py)s file3 zzz yyy xxx
+%(python)s %(cat_py)s file3.yyy zzz yyy xxx xxx
+""" % {'python':python, 'cat_py':cat_py}))
+
+test.must_match(['work1', 'src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
+test.must_match(['work1', 'src', 'file3.alt'], "zzz 2\nyyy 2\nxxx 1\n")
+test.must_match(['work1', 'src', 'file3.yyy'], "zzz 2\nyyy 2\nxxx 1\nxxx 1\n")
+
#
test.write(['work1', 'src', 'SConscript'], """\
Import("env")
""" % (python, cat_py))
test.run(chdir='work1/src',arguments=args, stdout=test.wrap_stdout("""\
-scons: rebuilding `file4' because the build action changed:
- old: %s %s file4 - file4.in
- new: %s %s file4 file4.in
-%s %s file4 file4.in
-""" % (python, cat_py,
- python, cat_py,
- python, cat_py)))
+scons: rebuilding `file4' because the contents of the build action changed
+ action: %(python)s %(cat_py)s $TARGET $FILE4FLAG $SOURCES
+%(python)s %(cat_py)s file4 file4.in
+""" % {'python':python, 'cat_py':cat_py})),
test.must_match(['work1', 'src', 'file4'], "file4.in 1\n")
arguments = "--debug=explain mode=1 .",
stdout = test.wrap_stdout("""\
scons: rebuilding `f1.out' because the build action changed:
- old: Copy("f1.out", "f1.in")
- new: DifferentCopy(["f1.out"], ["f1.in"])
- AltCopyStage2(["f1.out"], ["f1.in"])
+ old: Copy("$TARGET", "$SOURCE")
+ new: DifferentCopy(target, source, env)
+ AltCopyStage2(target, source, env)
DifferentCopy(["f1.out"], ["f1.in"])
AltCopyStage2(["f1.out"], ["f1.in"])
scons: rebuilding `f2.out' because the contents of the build action changed
- action: ChangingCopy(["f2.out"], ["f2.in"])
+ action: ChangingCopy(target, source, env)
ChangingCopy(["f2.out"], ["f2.in"])
"""))
Test various cases where a target is "built" by multiple builder calls.
"""
+import TestCmd
import TestSCons
import os.path
-test = TestSCons.TestSCons()
+test = TestSCons.TestSCons(match=TestCmd.match_re)
#
test.run(arguments='file2.out',
status=2,
- stderr="""
+ stderr=TestSCons.re_escape("""
scons: *** Multiple ways to build the same target were specified for: file2.out (from ['file2a.in'] and from ['file2b.in'])
-File "SConstruct", line 10, in ?
-""")
+""") + TestSCons.file_expr)
#
test.run(arguments='file3.out',
status=2,
- stderr="""
+ stderr=TestSCons.re_escape("""
scons: *** Two different sets of overrides were specified for the same target: file3.out
-File "SConstruct", line 10, in ?
-""")
+""") + TestSCons.file_expr)
#
test.write('file5b.in', 'file5b.in\n')
test.run(arguments='file5.out',
- stderr="""
+ stderr=TestSCons.re_escape("""
scons: warning: Two different environments were specified for target file5.out,
- but they appear to have the same action: build(["file5.out"], ["file5b.in"])
-File "SConstruct", line 11, in ?
-""")
+ but they appear to have the same action: build(target, source, env)
+""") + TestSCons.file_expr)
test.must_match('file5.out', "file5a.in\nfile5b.in\n")
test.run(arguments='file6.out',
status=2,
- stderr="""
+ stderr=TestSCons.re_escape("""
scons: *** Two environments with different actions were specified for the same target: file6.out
-File "SConstruct", line 11, in ?
-""")
+""") + TestSCons.file_expr)
#
test.run(arguments='file8.out',
status=2,
- stderr="""
+ stderr=TestSCons.re_escape("""
scons: *** Two different builders (B and C) were specified for the same target: file8.out
-File "SConstruct", line 14, in ?
-""")
+""") + TestSCons.file_expr)
#
test.run(arguments='file10.out',
status=2,
- stderr="""
+ stderr=TestSCons.re_escape("""
scons: *** Two different target sets have a target in common: file10b.out
-File "SConstruct", line 11, in ?
-""")
+""") + TestSCons.file_expr)
#
test.run(arguments='file11.out',
status=2,
- stderr="""
+ stderr=TestSCons.re_escape("""
scons: *** Two different target sets have a target in common: file11b.out
-File "SConstruct", line 11, in ?
-""")
+""") + TestSCons.file_expr)
#
test.run(arguments='file12.out',
status=2,
- stderr="""
+ stderr=TestSCons.re_escape("""
scons: *** Cannot build same target `file12a.out' as singular and list
-File "SConstruct", line 11, in ?
-""")
+""") + TestSCons.file_expr)
test.write('file18.in', "file18.in\n")
expect = """\
-Building file01.out with action(s):
+Building file01.out with action:
$PYTHON cat.py $SOURCES $TARGET
__PYTHON__ cat.py file01.in file01.out
-Building file02.out with action(s):
+Building file02.out with action:
$PYTHON cat.py $SOURCES $TARGET
__PYTHON__ cat.py file02.in file02.out
-Building file03.out with action(s):
+Building file03.out with action:
$PYTHON cat.py $SOURCES temp
- $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py file03.in temp
+Building file03.out with action:
+ $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py temp file03.out
-Building file04.out with action(s):
+Building file04.out with action:
$PYTHON cat.py $SOURCES temp
- $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py file04.in temp
+Building file04.out with action:
+ $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py temp file04.out
-Building file05.out with action(s):
+Building file05.out with action:
$PYTHON cat.py $SOURCES temp
- $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py file05.in temp
+Building file05.out with action:
+ $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py temp file05.out
-Building file06.out with action(s):
+Building file06.out with action:
$PYTHON cat.py $SOURCES temp
- $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py file06.in temp
+Building file06.out with action:
+ $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py temp file06.out
-Building file07.out with action(s):
- cat(env, target, source)
+Building file07.out with action:
+ cat(target, source, env)
cat(["file07.out"], ["file07.in"])
-Building file08.out with action(s):
- cat(env, target, source)
+Building file08.out with action:
+ cat(target, source, env)
cat(["file08.out"], ["file08.in"])
-Building file09.out with action(s):
- cat(env, target, source)
+Building file09.out with action:
+ cat(target, source, env)
cat(["file09.out"], ["file09.in"])
-Building file11.out with action(s):
+Building file11.out with action:
$PYTHON cat.py $SOURCES $TARGET
__PYTHON__ cat.py file11.in file11.out
-Building file12.out with action(s):
+Building file12.out with action:
$PYTHON cat.py $SOURCES $TARGET
__PYTHON__ cat.py file12.in file12.out
-Building file13.out with action(s):
+Building file13.out with action:
$PYTHON cat.py $SOURCES temp
- $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py file13.in temp
+Building file13.out with action:
+ $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py temp file13.out
-Building file14.out with action(s):
+Building file14.out with action:
$PYTHON cat.py $SOURCES temp
- $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py file14.in temp
+Building file14.out with action:
+ $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py temp file14.out
-Building file15.out with action(s):
+Building file15.out with action:
$PYTHON cat.py $SOURCES temp
- $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py file15.in temp
+Building file15.out with action:
+ $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py temp file15.out
-Building file16.out with action(s):
+Building file16.out with action:
$PYTHON cat.py $SOURCES temp
- $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py file16.in temp
+Building file16.out with action:
+ $PYTHON cat.py temp $TARGET
__PYTHON__ cat.py temp file16.out
-Building file17.out with action(s):
- cat(env, target, source)
+Building file17.out with action:
+ cat(target, source, env)
cat(["file17.out"], ["file17.in"])
-Building file18.out with action(s):
- cat(env, target, source)
+Building file18.out with action:
+ cat(target, source, env)
cat(["file18.out"], ["file18.in"])
"""
expect = string.replace(expect, '__PYTHON__', TestSCons.python)
test.run(arguments='--warn=dependency .', stderr=r"""
scons: warning: No dependency generated for file: not_there\.h \(included from: foo\.c\) \-\- file not found
-File ".+", line \d+, in .+
-""")
+""" + TestSCons.file_expr)
test.run(arguments='--warn=all .', stderr=r"""
scons: warning: No dependency generated for file: not_there\.h \(included from: foo\.c\) \-\- file not found
-File ".+", line \d+, in .+
-""")
+""" + TestSCons.file_expr)
test.run(arguments='--warn=all --warn=no-dependency .', stderr="")
test.run(arguments='--warn=no-dependency --warn=all .', stderr=r"""
scons: warning: No dependency generated for file: not_there\.h \(included from: foo\.c\) \-\- file not found
-File ".+", line \d+, in .+
-""")
+""" + TestSCons.file_expr)
test.run(arguments = '--warn=missing-sconscript .', stderr = r"""
scons: warning: Ignoring missing SConscript 'no_such_file'
-File ".+", line \d+, in .+
-""")
+""" + TestSCons.file_expr)
test.run(arguments = '--warn=no-missing-sconscript .', stderr = "")
test.run(arguments='file1.out',
stderr=r"""
scons: warning: Two different environments were specified for target file1.out,
- but they appear to have the same action: build\(\["file1.out"\], \["file1b.in"\]\)
-File "SConstruct", line \d+, in .+
-""")
+ but they appear to have the same action: build\(target, source, env\)
+""" + TestSCons.file_expr)
test.must_match('file1.out', "file1a.in\nfile1b.in\n")
test.run(arguments='--warn=duplicate-environment file1.out',
stderr=r"""
scons: warning: Two different environments were specified for target file1.out,
- but they appear to have the same action: build\(\["file1.out"\], \["file1b.in"\]\)
-File "SConstruct", line \d+, in .+
-""")
+ but they appear to have the same action: build\(target, source, env\)
+""" + TestSCons.file_expr)
test.run(arguments='--warn=no-duplicate-environment file1.out')
test.run(arguments='.',
stderr=r"""
scons: warning: Did you mean to use `(target|source)' instead of `(targets|sources)'\?
-File "SConstruct", line \d+, in .+
-
+""" + TestSCons.file_expr + r"""
scons: warning: Did you mean to use `(target|source)' instead of `(targets|sources)'\?
-File "SConstruct", line \d+, in .+
-""")
+""" + TestSCons.file_expr)
test.must_match(['file3a'], 'file3a.in\n')
test.must_match(['file3b'], 'file3b.out\n')
test.run(arguments='--warn=misleading-keywords .',
stderr=r"""
scons: warning: Did you mean to use `(target|source)' instead of `(targets|sources)'\?
-File "SConstruct", line \d+, in .+
-
+""" + TestSCons.file_expr + r"""\
scons: warning: Did you mean to use `(target|source)' instead of `(targets|sources)'\?
-File "SConstruct", line \d+, in .+
-""")
+""" + TestSCons.file_expr)
test.run(arguments='--warn=no-misleading-keywords .')
%s cat.py dict7.list .temp
%s cat.py .temp dict7.out
Building dict8.out from dict8.liststr
+Building dict8.out from dict8.liststr
func(["func.out"], ["func.in"])
Building funcstr.out from funcstr.in
%s cat.py lazy.in lazy.out
%s cat.py list.in .temp
%s cat.py .temp list.out
Building liststr.out from liststr.in
+Building liststr.out from liststr.in
""") % (python, python, python, python, python, python, python, python))
test.pass_test()