http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / ActionTests.py
index bc8e39f83afcc52fbfdb6d46107294f7e0c0bfbd..4ffbdecf2b375637468d647d1e9519c64071379c 100644 (file)
@@ -23,6 +23,8 @@
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.compat
+
 # Define a null function and a null class for use as builder actions.
 # Where these are defined in the file seems to affect their byte-code
 # contents, so try to minimize changes by defining them here, before we
@@ -34,14 +36,13 @@ class GlobalActFunc:
     def __call__(self):
         pass
 
+import collections
+import io
 import os
 import re
-import StringIO
-import string
 import sys
 import types
 import unittest
-import UserDict
 
 import SCons.Action
 import SCons.Environment
@@ -58,7 +59,8 @@ import TestCmd
 # for each test, they can just use the one.
 test = TestCmd.TestCmd(workdir = '')
 
-test.write('act.py', """import os, string, sys
+test.write('act.py', """\
+import os, string, sys
 f = open(sys.argv[1], 'w')
 f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n")
 try:
@@ -67,21 +69,27 @@ try:
 except:
     pass
 f.close()
-if os.environ.has_key( 'ACTPY_PIPE' ):
-    if os.environ.has_key( 'PIPE_STDOUT_FILE' ):
+if 'ACTPY_PIPE' in os.environ:
+    if 'PIPE_STDOUT_FILE' in os.environ:
          stdout_msg = open(os.environ['PIPE_STDOUT_FILE'], 'r').read()
     else:
-         stdout_msg = "act.py: stdout: executed act.py %s\\n" % string.join(sys.argv[1:])
+         stdout_msg = "act.py: stdout: executed act.py %s\\n" % ' '.join(sys.argv[1:])
     sys.stdout.write( stdout_msg )
-    if os.environ.has_key( 'PIPE_STDERR_FILE' ):
+    if 'PIPE_STDERR_FILE' in os.environ:
          stderr_msg = open(os.environ['PIPE_STDERR_FILE'], 'r').read()
     else:
-         stderr_msg = "act.py: stderr: executed act.py %s\\n" % string.join(sys.argv[1:])
+         stderr_msg = "act.py: stderr: executed act.py %s\\n" % ' '.join(sys.argv[1:])
     sys.stderr.write( stderr_msg )
 sys.exit(0)
 """)
 
+test.write('exit.py', """\
+import sys
+sys.exit(int(sys.argv[1]))
+""")
+
 act_py = test.workpath('act.py')
+exit_py = test.workpath('exit.py')
 
 outfile = test.workpath('outfile')
 outfile2 = test.workpath('outfile2')
@@ -91,7 +99,7 @@ scons_env = SCons.Environment.Environment()
 
 # Capture all the stuff the Actions will print,
 # so it doesn't clutter the output.
-sys.stdout = StringIO.StringIO()
+sys.stdout = io.StringIO()
 
 class CmdStringHolder:
     def __init__(self, cmd, literal=None):
@@ -128,26 +136,28 @@ class Environment:
         for k, v in kw.items():
             self.d[k] = v
     # Just use the underlying scons_subst*() utility methods.
-    def subst(self, strSubst, raw=0, target=[], source=[], dict=None):
-        return SCons.Util.scons_subst(strSubst, self, raw, target, source, dict)
+    def subst(self, strSubst, raw=0, target=[], source=[], conv=None):
+        return SCons.Subst.scons_subst(strSubst, self, raw,
+                                       target, source, self.d, conv=conv)
     subst_target_source = subst
-    def subst_list(self, strSubst, raw=0, target=[], source=[], dict=None):
-        return SCons.Util.scons_subst_list(strSubst, self, raw, target, source, dict)
+    def subst_list(self, strSubst, raw=0, target=[], source=[], conv=None):
+        return SCons.Subst.scons_subst_list(strSubst, self, raw,
+                                       target, source, self.d, conv=conv)
     def __getitem__(self, item):
         return self.d[item]
     def __setitem__(self, item, value):
         self.d[item] = value
     def has_key(self, item):
-        return self.d.has_key(item)
-    def get(self, key, value):
+        return item in self.d
+    def get(self, key, value=None):
         return self.d.get(key, value)
     def items(self):
         return self.d.items()
     def Dictionary(self):
         return self.d
-    def Copy(self, **kw):
+    def Clone(self, **kw):
         res = Environment()
-        res.d = SCons.Environment.our_deepcopy(self.d)
+        res.d = SCons.Util.semi_deepcopy(self.d)
         for k, v in kw.items():
             res.d[k] = v
         return res
@@ -163,6 +173,8 @@ class Environment:
 class DummyNode:
     def __init__(self, name):
         self.name = name
+    def str_for_display(self):
+        return '"' + self.name + '"'
     def __str__(self):
         return self.name
     def rfile(self):
@@ -175,76 +187,314 @@ if os.name == 'java':
 else:
     python = sys.executable
 
+_python_ = '"' + python + '"'
+
+_null = SCons.Action._null
+
+def test_varlist(pos_call, str_call, cmd, cmdstrfunc, **kw):
+    def call_action(a, pos_call=pos_call, str_call=str_call, kw=kw):
+        a = SCons.Action.Action(*a, **kw)
+        # returned object must provide these entry points
+        assert hasattr(a, '__call__')
+        assert hasattr(a, 'get_contents')
+        assert hasattr(a, 'genstring')
+        pos_call(a)
+        str_call(a)
+        return a
+
+    a = call_action((cmd, cmdstrfunc))
+    assert a.varlist == (), a.varlist
+
+    a = call_action((cmd, cmdstrfunc, 'foo'))
+    assert a.varlist == ('foo',), a.varlist
+
+    a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c'))
+    assert a.varlist == ('a', 'b', 'c'), a.varlist
+
+    kw['varlist'] = 'foo'
+    a = call_action((cmd, cmdstrfunc))
+    assert a.varlist == ('foo',), a.varlist
+
+    kw['varlist'] = ['x', 'y', 'z']
+    a = call_action((cmd, cmdstrfunc))
+    assert a.varlist == ('x', 'y', 'z'), a.varlist
+
+    a = call_action((cmd, cmdstrfunc, 'foo'))
+    assert a.varlist == ('foo', 'x', 'y', 'z'), a.varlist
+
+    a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c'))
+    assert a.varlist == ('a', 'b', 'c', 'x', 'y', 'z'), a.varlist
+
+def test_positional_args(pos_callback, cmd, **kw):
+    """Test that Action() returns the expected type and that positional args work.
+    """
+    #FUTURE act = SCons.Action.Action(cmd, **kw)
+    act = SCons.Action.Action(cmd, **kw)
+    pos_callback(act)
+    assert act.varlist is (), act.varlist
+
+    if not isinstance(act, SCons.Action._ActionAction):
+        # only valid cmdstrfunc is None
+        def none(a): pass
+        #FUTURE test_varlist(pos_callback, none, cmd, None, **kw)
+        test_varlist(pos_callback, none, cmd, None, **kw)
+    else:
+        # _ActionAction should have set these
+        assert hasattr(act, 'strfunction')
+        assert act.cmdstr is _null, act.cmdstr
+        assert act.presub is _null, act.presub
+        assert act.chdir is None, act.chdir
+        assert act.exitstatfunc is SCons.Action.default_exitstatfunc, \
+                                        act.exitstatfunc
+
+        def cmdstr(a):
+            assert hasattr(a, 'strfunction')
+            assert a.cmdstr == 'cmdstr', a.cmdstr
+        #FUTURE test_varlist(pos_callback, cmdstr, cmd, 'cmdstr', **kw)
+        test_varlist(pos_callback, cmdstr, cmd, 'cmdstr', **kw)
+
+        def fun(): pass
+        def strfun(a, fun=fun):
+            assert a.strfunction is fun, a.strfunction
+            assert a.cmdstr == _null, a.cmdstr
+        #FUTURE test_varlist(pos_callback, strfun, cmd, fun, **kw)
+        test_varlist(pos_callback, strfun, cmd, fun, **kw)
+
+        def none(a):
+            assert hasattr(a, 'strfunction')
+            assert a.cmdstr is None, a.cmdstr
+        #FUTURE test_varlist(pos_callback, none, cmd, None, **kw)
+        test_varlist(pos_callback, none, cmd, None, **kw)
+
+        """Test handling of bad cmdstrfunc arguments """
+        try:
+            #FUTURE a = SCons.Action.Action(cmd, [], **kw)
+            a = SCons.Action.Action(cmd, [], **kw)
+        except SCons.Errors.UserError, e:
+            s = str(e)
+            m = 'Invalid command display variable'
+            assert s.find(m) != -1, 'Unexpected string:  %s' % s
+        else:
+            raise Exception("did not catch expected UserError")
+
+    return act
+
 class ActionTestCase(unittest.TestCase):
+    """Test the Action() factory function"""
 
-    def test_factory(self):
-        """Test the Action factory
+    def test_FunctionAction(self):
+        """Test the Action() factory's creation of FunctionAction objects
         """
         def foo():
             pass
-        def bar():
-            pass
-        a1 = SCons.Action.Action(foo)
-        assert isinstance(a1, SCons.Action.FunctionAction), a1
-        assert a1.execfunction == foo, a1.execfunction
 
-        a2 = SCons.Action.Action("string")
-        assert isinstance(a2, SCons.Action.CommandAction), a2
-        assert a2.cmd_list == "string", a2.cmd_list
+        def func_action(a, foo=foo):
+            assert isinstance(a, SCons.Action.FunctionAction), a
+            assert a.execfunction == foo, a.execfunction
+        test_positional_args(func_action, foo)
+        # a singleton list returns the contained action
+        test_positional_args(func_action, [foo])
+
+    def test_CommandAction(self):
+        """Test the Action() factory's creation of CommandAction objects
+        """
+        def cmd_action(a):
+            assert isinstance(a, SCons.Action.CommandAction), a
+            assert a.cmd_list == "string", a.cmd_list
+        test_positional_args(cmd_action, "string")
+        # a singleton list returns the contained action
+        test_positional_args(cmd_action, ["string"])
+
+        try: unicode
+        except NameError: pass
+        else:
+            a2 = eval("SCons.Action.Action(u'string')")
+            assert isinstance(a2, SCons.Action.CommandAction), a2
 
-        if hasattr(types, 'UnicodeType'):
-            exec "a3 = SCons.Action.Action(u'string')"
-            exec "assert isinstance(a3, SCons.Action.CommandAction), a3"
+        def line_action(a):
+            assert isinstance(a, SCons.Action.CommandAction), a
+            assert a.cmd_list == [ "explicit", "command", "line" ], a.cmd_list
+        test_positional_args(line_action, [[ "explicit", "command", "line" ]])
 
-        a4 = SCons.Action.Action(["x", "y", "z", [ "a", "b", "c"]])
+    def test_ListAction(self):
+        """Test the Action() factory's creation of ListAction objects
+        """
+        a1 = SCons.Action.Action(["x", "y", "z", [ "a", "b", "c"]])
+        assert isinstance(a1, SCons.Action.ListAction), a1
+        assert a1.varlist is (), a1.varlist
+        assert isinstance(a1.list[0], SCons.Action.CommandAction), a1.list[0]
+        assert a1.list[0].cmd_list == "x", a1.list[0].cmd_list
+        assert isinstance(a1.list[1], SCons.Action.CommandAction), a1.list[1]
+        assert a1.list[1].cmd_list == "y", a1.list[1].cmd_list
+        assert isinstance(a1.list[2], SCons.Action.CommandAction), a1.list[2]
+        assert a1.list[2].cmd_list == "z", a1.list[2].cmd_list
+        assert isinstance(a1.list[3], SCons.Action.CommandAction), a1.list[3]
+        assert a1.list[3].cmd_list == [ "a", "b", "c" ], a1.list[3].cmd_list
+
+        a2 = SCons.Action.Action("x\ny\nz")
+        assert isinstance(a2, SCons.Action.ListAction), a2
+        assert a2.varlist is (), a2.varlist
+        assert isinstance(a2.list[0], SCons.Action.CommandAction), a2.list[0]
+        assert a2.list[0].cmd_list == "x", a2.list[0].cmd_list
+        assert isinstance(a2.list[1], SCons.Action.CommandAction), a2.list[1]
+        assert a2.list[1].cmd_list == "y", a2.list[1].cmd_list
+        assert isinstance(a2.list[2], SCons.Action.CommandAction), a2.list[2]
+        assert a2.list[2].cmd_list == "z", a2.list[2].cmd_list
+
+        def foo():
+            pass
+
+        a3 = SCons.Action.Action(["x", foo, "z"])
+        assert isinstance(a3, SCons.Action.ListAction), a3
+        assert a3.varlist is (), a3.varlist
+        assert isinstance(a3.list[0], SCons.Action.CommandAction), a3.list[0]
+        assert a3.list[0].cmd_list == "x", a3.list[0].cmd_list
+        assert isinstance(a3.list[1], SCons.Action.FunctionAction), a3.list[1]
+        assert a3.list[1].execfunction == foo, a3.list[1].execfunction
+        assert isinstance(a3.list[2], SCons.Action.CommandAction), a3.list[2]
+        assert a3.list[2].cmd_list == "z", a3.list[2].cmd_list
+
+        a4 = SCons.Action.Action(["x", "y"], strfunction=foo)
         assert isinstance(a4, SCons.Action.ListAction), a4
+        assert a4.varlist is (), a4.varlist
         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 isinstance(a4.list[2], SCons.Action.CommandAction), a4.list[2]
-        assert a4.list[2].cmd_list == "z", a4.list[2].cmd_list
-        assert isinstance(a4.list[3], SCons.Action.CommandAction), a4.list[3]
-        assert a4.list[3].cmd_list == [ "a", "b", "c" ], a4.list[3].cmd_list
+        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 a5.varlist is (), a5.varlist
+        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.list[1].strfunction == foo, a5.list[1].strfunction
+
+    def test_CommandGeneratorAction(self):
+        """Test the Action() factory's creation of CommandGeneratorAction objects
+        """
+        def foo(): pass
+
+        def gen_action(a, foo=foo):
+            assert isinstance(a, SCons.Action.CommandGeneratorAction), a
+            assert a.generator is foo, a.generator
+        test_positional_args(gen_action, foo, generator=1)
 
+    def test_LazyCmdGeneratorAction(self):
+        """Test the Action() factory's creation of lazy CommandGeneratorAction objects
+        """
+        def lazy_action(a):
+            assert isinstance(a, SCons.Action.LazyAction), a
+            assert a.var == "FOO", a.var
+            assert a.cmd_list == "${FOO}", a.cmd_list
+        test_positional_args(lazy_action, "$FOO")
+        test_positional_args(lazy_action, "${FOO}")
+
+    def test_no_action(self):
+        """Test when the Action() factory can't create an action object
+        """
         a5 = SCons.Action.Action(1)
         assert a5 is None, a5
 
-        a6 = SCons.Action.Action(a1)
-        assert a6 is a1, a6
-
-        a7 = SCons.Action.Action([[ "explicit", "command", "line" ]])
-        assert isinstance(a7, SCons.Action.CommandAction), a7
-        assert a7.cmd_list == [ "explicit", "command", "line" ], a7.cmd_list
-
-        a8 = SCons.Action.Action(["a8"])
-        assert isinstance(a8, SCons.Action.CommandAction), a8
-        assert a8.cmd_list == "a8", a8.cmd_list
-
-        a9 = SCons.Action.Action("x\ny\nz")
-        assert isinstance(a9, SCons.Action.ListAction), a9
-        assert isinstance(a9.list[0], SCons.Action.CommandAction), a9.list[0]
-        assert a9.list[0].cmd_list == "x", a9.list[0].cmd_list
-        assert isinstance(a9.list[1], SCons.Action.CommandAction), a9.list[1]
-        assert a9.list[1].cmd_list == "y", a9.list[1].cmd_list
-        assert isinstance(a9.list[2], SCons.Action.CommandAction), a9.list[2]
-        assert a9.list[2].cmd_list == "z", a9.list[2].cmd_list
-
-        a10 = SCons.Action.Action(["x", foo, "z"])
-        assert isinstance(a10, SCons.Action.ListAction), a10
-        assert isinstance(a10.list[0], SCons.Action.CommandAction), a10.list[0]
-        assert a10.list[0].cmd_list == "x", a10.list[0].cmd_list
-        assert isinstance(a10.list[1], SCons.Action.FunctionAction), a10.list[1]
-        assert a10.list[1].execfunction == foo, a10.list[1].execfunction
-        assert isinstance(a10.list[2], SCons.Action.CommandAction), a10.list[2]
-        assert a10.list[2].cmd_list == "z", a10.list[2].cmd_list
-
-        a11 = SCons.Action.Action(foo, strfunction=bar)
-        assert isinstance(a11, SCons.Action.FunctionAction), a11
-        assert a11.execfunction == foo, a11.execfunction
-        assert a11.strfunction == bar, a11.strfunction
-
-class ActionBaseTestCase(unittest.TestCase):
+    def test_reentrance(self):
+        """Test the Action() factory when the action is already an Action object
+        """
+        a1 = SCons.Action.Action("foo")
+        a2 = SCons.Action.Action(a1)
+        assert a2 is a1, a2
+
+class _ActionActionTestCase(unittest.TestCase):
+
+    def test__init__(self):
+        """Test creation of _ActionAction objects
+        """
+
+        def func1():
+            pass
+
+        def func2():
+            pass
+
+        def func3():
+            pass
+
+        a = SCons.Action._ActionAction()
+        assert not hasattr(a, 'strfunction')
+        assert a.cmdstr is _null, a.cmdstr
+        assert a.varlist == (), a.varlist
+        assert a.presub is _null, a.presub
+        assert a.chdir is None, a.chdir
+        assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc
+
+        assert SCons.Action._ActionAction(kwarg = 1)
+        assert not hasattr(a, 'kwarg')
+        assert not hasattr(a, 'strfunction')
+        assert a.cmdstr is _null, a.cmdstr
+        assert a.varlist == (), a.varlist
+        assert a.presub is _null, a.presub
+        assert a.chdir is None, a.chdir
+        assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc
+
+        a = SCons.Action._ActionAction(strfunction=func1)
+        assert a.strfunction is func1, a.strfunction
+
+        a = SCons.Action._ActionAction(strfunction=None)
+        assert not hasattr(a, 'strfunction')
+        assert a.cmdstr is None, a.cmdstr
+
+        a = SCons.Action._ActionAction(cmdstr='cmdstr')
+        assert not hasattr(a, 'strfunction')
+        assert a.cmdstr is 'cmdstr', a.cmdstr
+
+        a = SCons.Action._ActionAction(cmdstr=None)
+        assert not hasattr(a, 'strfunction')
+        assert a.cmdstr is None, a.cmdstr
+
+        t = ('a','b','c')
+        a = SCons.Action._ActionAction(varlist=t)
+        assert a.varlist == t, a.varlist
+
+        a = SCons.Action._ActionAction(presub=func1)
+        assert a.presub is func1, a.presub
+
+        a = SCons.Action._ActionAction(chdir=1)
+        assert a.chdir is 1, a.chdir
+
+        a = SCons.Action._ActionAction(exitstatfunc=func1)
+        assert a.exitstatfunc is func1, a.exitstatfunc
+
+        a = SCons.Action._ActionAction(
+                                 # alphabetical order ...
+                                 chdir='x',
+                                 cmdstr='cmdstr',
+                                 exitstatfunc=func3,
+                                 presub=func2,
+                                 strfunction=func1,
+                                 varlist=t,
+                          )
+        assert a.chdir is 'x', a.chdir
+        assert a.cmdstr is 'cmdstr', a.cmdstr
+        assert a.exitstatfunc is func3, a.exitstatfunc
+        assert a.presub is func2, a.presub
+        assert a.strfunction is func1, a.strfunction
+        assert a.varlist is t, a.varlist
+
+    def test_dup_keywords(self):
+        """Test handling of both cmdstr and strfunction arguments
+        """
+        def func(): pass
+        try:
+            a = SCons.Action.Action('foo', cmdstr='string', strfunction=func)
+        except SCons.Errors.UserError, e:
+            s = str(e)
+            m = 'Cannot have both strfunction and cmdstr args to Action()'
+            assert s.find(m) != -1, 'Unexpected string:  %s' % s
+        else:
+            raise Exception("did not catch expected UserError")
 
     def test___cmp__(self):
         """Test Action comparison
@@ -256,119 +506,239 @@ class ActionBaseTestCase(unittest.TestCase):
         assert a1 != a3
         assert a2 != a3
 
-    def test_show(self):
-        """Test the show() method
+    def test_print_cmd_lines(self):
+        """Test the print_cmd_lines() method
         """
         save_stdout = sys.stdout
 
-        save_print_actions = SCons.Action.print_actions
-        SCons.Action.print_actions = 0
-
         try:
-            a = SCons.Action.Action("x")
-
-            sio = StringIO.StringIO()
-            sys.stdout = sio
-            a.show("xyzzy")
-            s = sio.getvalue()
-            assert s == "", s
-
-            SCons.Action.print_actions = 1
+            def execfunc(target, source, env):
+                pass
+            a = SCons.Action.Action(execfunc)
 
-            sio = StringIO.StringIO()
+            sio = io.StringIO()
             sys.stdout = sio
-            a.show("foobar")
+            a.print_cmd_line("foo bar", None, None, None)
             s = sio.getvalue()
-            assert s == "foobar\n", s
+            assert s == "foo bar\n", s
 
         finally:
-            SCons.Action.print_actions = save_print_actions
             sys.stdout = save_stdout
 
-    def test_presub(self):
-        """Test the presub() method
+    def test___call__(self):
+        """Test calling an Action
         """
         save_stdout = sys.stdout
 
+        save_print_actions = SCons.Action.print_actions
         save_print_actions_presub = SCons.Action.print_actions_presub
-        SCons.Action.print_actions_presub = 0
+        save_execute_actions = SCons.Action.execute_actions
+        #SCons.Action.print_actions = 0
+
+        test = TestCmd.TestCmd(workdir = '')
+        test.subdir('sub', 'xyz')
+        os.chdir(test.workpath())
 
         try:
-            a = SCons.Action.Action("x")
             env = Environment()
 
-            sio = StringIO.StringIO()
+            def execfunc(target, source, env):
+                assert isinstance(target, list), type(target)
+                assert isinstance(source, list), type(source)
+                return 7
+            a = SCons.Action.Action(execfunc)
+
+            def firstfunc(target, source, env):
+                assert isinstance(target, list), type(target)
+                assert isinstance(source, list), type(source)
+                return 0
+            def lastfunc(target, source, env):
+                assert isinstance(target, list), type(target)
+                assert isinstance(source, list), type(source)
+                return 9
+            b = SCons.Action.Action([firstfunc, execfunc, lastfunc])
+            
+            sio = io.StringIO()
             sys.stdout = sio
-            a.presub("xyzzy", env)
+            result = a("out", "in", env)
+            assert result.status == 7, result
             s = sio.getvalue()
-            assert s == "", s
+            assert s == "execfunc(['out'], ['in'])\n", s
+
+            a.chdir = 'xyz'
+            expect = "os.chdir(%s)\nexecfunc(['out'], ['in'])\nos.chdir(%s)\n"
+
+            sio = io.StringIO()
+            sys.stdout = sio
+            result = a("out", "in", env)
+            assert result.status == 7, result.status
+            s = sio.getvalue()
+            assert s == expect % (repr('xyz'), repr(test.workpath())), s
+
+            sio = io.StringIO()
+            sys.stdout = sio
+            result = a("out", "in", env, chdir='sub')
+            assert result.status == 7, result.status
+            s = sio.getvalue()
+            assert s == expect % (repr('sub'), repr(test.workpath())), s
+
+            a.chdir = None
+
+            sio = io.StringIO()
+            sys.stdout = sio
+            result = b("out", "in", env)
+            assert result.status == 7, result.status
+            s = sio.getvalue()
+            assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\n", s
+
+            SCons.Action.execute_actions = 0
+
+            sio = io.StringIO()
+            sys.stdout = sio
+            result = a("out", "in", env)
+            assert result == 0, result
+            s = sio.getvalue()
+            assert s == "execfunc(['out'], ['in'])\n", s
+
+            sio = io.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()
+            sio = io.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = a("out", "in", env)
+            assert result.status == 7, result.status
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  x\n", s
+            assert s == "Building out with action:\n  execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
 
-            a = SCons.Action.Action(["y", "z"])
+            sio = io.StringIO()
+            sys.stdout = sio
+            result = a("out", "in", env, presub=0)
+            assert result.status == 7, result.status
+            s = sio.getvalue()
+            assert s == "execfunc(['out'], ['in'])\n", s
 
-            sio = StringIO.StringIO()
+            sio = io.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = a("out", "in", env, presub=1)
+            assert result.status == 7, result.status
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  y\n  z\n", s
+            assert s == "Building out with action:\n  execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
 
-            def func():
-                pass
-            a = SCons.Action.Action(func)
+            sio = io.StringIO()
+            sys.stdout = sio
+            result = b(["out"], "in", env, presub=1)
+            assert result.status == 7, result.status
+            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()
+            sio = io.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = b(["out", "list"], "in", env, presub=1)
+            assert result.status == 7, result.status
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  func(env, target, source)\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
 
-            def gen(target, source, env, for_signature):
-                return 'generat' + env.get('GEN', 'or')
-            a = SCons.Action.Action(SCons.Action.CommandGenerator(gen))
+            a2 = SCons.Action.Action(execfunc)
 
-            sio = StringIO.StringIO()
+            sio = io.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = a2("out", "in", env)
+            assert result.status == 7, result.status
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  generator\n", s
+            assert s == "Building out with action:\n  execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
 
-            sio = StringIO.StringIO()
+            sio = io.StringIO()
             sys.stdout = sio
-            a.presub("foobar", Environment(GEN = 'ed'))
+            result = a2("out", "in", env, presub=0)
+            assert result.status == 7, result.status
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  generated\n", s
+            assert s == "execfunc(['out'], ['in'])\n", s
 
-            a = SCons.Action.Action("$ACT")
+            SCons.Action.execute_actions = 0
 
-            sio = StringIO.StringIO()
+            sio = io.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = a2("out", "in", env, presub=0)
+            assert result == 0, result
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  \n", s
+            assert s == "execfunc(['out'], ['in'])\n", s
 
-            sio = StringIO.StringIO()
+            sio = io.StringIO()
             sys.stdout = sio
-            a.presub("foobar", Environment(ACT = 'expanded action'))
+            result = a("out", "in", env, presub=0, execute=1, show=0)
+            assert result.status == 7, result.status
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  expanded action\n", s
+            assert s == '', s
+
+            sys.stdout = save_stdout
+            exitstatfunc_result = []
+
+            def exitstatfunc(stat, result=exitstatfunc_result):
+                result.append(stat)
+                return stat
+
+            result = a("out", "in", env, exitstatfunc=exitstatfunc)
+            assert result == 0, result
+            assert exitstatfunc_result == [], exitstatfunc_result
+
+            result = a("out", "in", env, execute=1, exitstatfunc=exitstatfunc)
+            assert result.status == 7, result.status
+            assert exitstatfunc_result == [7], exitstatfunc_result
+
+            SCons.Action.execute_actions = 1
+
+            result = []
+            def my_print_cmd_line(s, target, source, env, result=result):
+                result.append(s)
+            env['PRINT_CMD_LINE_FUNC'] = my_print_cmd_line
+            a("output", "input", env)
+            assert result == ["execfunc(['output'], ['input'])"], result
+            
 
         finally:
-            SCons.Action.print_actions_presub = save_print_actions_presub
             sys.stdout = save_stdout
+            SCons.Action.print_actions = save_print_actions
+            SCons.Action.print_actions_presub = save_print_actions_presub
+            SCons.Action.execute_actions = save_execute_actions
 
-    def test_get_actions(self):
-        """Test the get_actions() method
+    def test_presub_lines(self):
+        """Test the presub_lines() method
         """
+        env = Environment()
         a = SCons.Action.Action("x")
-        l = a.get_actions()
-        assert l == [a], l
+        s = a.presub_lines(env)
+        assert s == ['x'], s
+
+        a = SCons.Action.Action(["y", "z"])
+        s = a.presub_lines(env)
+        assert s == ['y', 'z'], s
+
+        def func():
+            pass
+        a = SCons.Action.Action(func)
+        s = a.presub_lines(env)
+        assert s == ["func(target, source, env)"], s
+
+        def gen(target, source, env, for_signature):
+            return 'generat' + env.get('GEN', 'or')
+        a = SCons.Action.Action(gen, generator=1)
+        s = a.presub_lines(env)
+        assert s == ["generator"], s
+        s = a.presub_lines(Environment(GEN = 'ed'))
+        assert s == ["generated"], s
+
+        a = SCons.Action.Action("$ACT")
+        s = a.presub_lines(env)
+        assert s == [''], s
+        s = a.presub_lines(Environment(ACT = 'expanded action'))
+        assert s == ['expanded action'], s
 
     def test_add(self):
         """Test adding Actions to stuff."""
@@ -377,15 +747,14 @@ class ActionBaseTestCase(unittest.TestCase):
         # containing all the Actions.
         def bar():
             return None
-        baz = SCons.Action.CommandGenerator(bar)
+        baz = SCons.Action.Action(bar, generator=1)
         act1 = SCons.Action.Action('foo bar')
         act2 = SCons.Action.Action([ 'foo', bar ])
 
         sum = act1 + act2
         assert isinstance(sum, SCons.Action.ListAction), str(sum)
         assert len(sum.list) == 3, len(sum.list)
-        assert map(lambda x: isinstance(x, SCons.Action.ActionBase),
-                   sum.list) == [ 1, 1, 1 ]
+        assert [isinstance(x, SCons.Action.ActionBase) for x in sum.list] == [ 1, 1, 1 ]
 
         sum = act1 + act1
         assert isinstance(sum, SCons.Action.ListAction), str(sum)
@@ -454,6 +823,11 @@ class CommandActionTestCase(unittest.TestCase):
         """
         a = SCons.Action.CommandAction(["xyzzy"])
         assert a.cmd_list == [ "xyzzy" ], a.cmd_list
+        assert a.cmdstr is _null, a.cmdstr
+
+        a = SCons.Action.CommandAction(["abra"], cmdstr="cadabra")
+        assert a.cmd_list == [ "abra" ], a.cmd_list
+        assert a.cmdstr == "cadabra", a.cmdstr
 
     def test___str__(self):
         """Test fetching the pre-substitution string for command Actions
@@ -467,7 +841,7 @@ class CommandActionTestCase(unittest.TestCase):
                                           '$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
@@ -499,7 +873,7 @@ class CommandActionTestCase(unittest.TestCase):
         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)
@@ -518,29 +892,136 @@ class CommandActionTestCase(unittest.TestCase):
         s2 = DummyNode('s2')
         act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
         s = act.strfunction([], [], env)
-        assert s == ['xyzzy'], s
+        assert s == 'xyzzy', s
         s = act.strfunction([t1], [s1], env)
-        assert s == ['xyzzy t1 s1'], s
+        assert s == 'xyzzy t1 s1', s
         s = act.strfunction([t1, t2], [s1, s2], env)
-        assert s == ['xyzzy t1 s1'], s
+        assert s == 'xyzzy t1 s1', s
+
+        act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE',
+                                         cmdstr='cmdstr - $SOURCE - $TARGET -')
+        s = act.strfunction([], [], env)
+        assert s == 'cmdstr -  -  -', s
+        s = act.strfunction([t1], [s1], env)
+        assert s == 'cmdstr - s1 - t1 -', s
+        s = act.strfunction([t1, t2], [s1, s2], env)
+        assert s == 'cmdstr - s1 - t1 -', s
 
         act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
         s = act.strfunction([], [], env)
-        assert s == ['xyzzy'], s
+        assert s == 'xyzzy', s
+        s = act.strfunction([t1], [s1], env)
+        assert s == 'xyzzy t1 s1', s
+        s = act.strfunction([t1, t2], [s1, s2], env)
+        assert s == 'xyzzy t1 t2 s1 s2', s
+
+        act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES',
+                                         cmdstr='cmdstr = $SOURCES = $TARGETS =')
+        s = act.strfunction([], [], env)
+        assert s == 'cmdstr =  =  =', s
         s = act.strfunction([t1], [s1], env)
-        assert s == ['xyzzy t1 s1'], s
+        assert s == 'cmdstr = s1 = t1 =', s
         s = act.strfunction([t1, t2], [s1, s2], env)
-        assert s == ['xyzzy t1 t2 s1 s2'], s
+        assert s == 'cmdstr = s1 s2 = t1 t2 =', s
 
         act = SCons.Action.CommandAction(['xyzzy',
                                           '$TARGET', '$SOURCE',
                                           '$TARGETS', '$SOURCES'])
         s = act.strfunction([], [], env)
-        assert s == ['xyzzy'], s
+        assert s == 'xyzzy', s
         s = act.strfunction([t1], [s1], env)
-        assert s == ['xyzzy t1 s1 t1 s1'], s
+        assert s == 'xyzzy t1 s1 t1 s1', s
         s = act.strfunction([t1, t2], [s1, s2], env)
-        assert s == ['xyzzy t1 s1 t1 t2 s1 s2'], s
+        assert s == 'xyzzy t1 s1 t1 t2 s1 s2', s
+
+        act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES',
+                                         cmdstr='cmdstr\t$TARGETS\n$SOURCES   ')
+                    
+        s = act.strfunction([], [], env)
+        assert s == 'cmdstr\t\n   ', s
+        s = act.strfunction([t1], [s1], env)
+        assert s == 'cmdstr\tt1\ns1   ', s
+        s = act.strfunction([t1, t2], [s1, s2], env)
+        assert s == 'cmdstr\tt1 t2\ns1 s2   ', s
+
+        def sf(target, source, env):
+            return "sf was called"
+        act = SCons.Action.CommandAction('foo', strfunction=sf)
+        s = act.strfunction([], [], env)
+        assert s == "sf was called", s
+
+        class actclass1:
+            def __init__(self, targets, sources, env):
+                pass
+            def __call__(self):
+                return 1
+        class actclass2:
+            def __init__(self, targets, sources, env):
+                self.strfunction = 5
+            def __call__(self):
+                return 2
+        class actclass3:
+            def __init__(self, targets, sources, env):
+                pass
+            def __call__(self):
+                return 3
+            def strfunction(self, targets, sources, env):
+                return 'actclass3 on %s to get %s'%(str(sources[0]),
+                                                    str(targets[0]))
+        class actclass4:
+            def __init__(self, targets, sources, env):
+                pass
+            def __call__(self):
+                return 4
+            strfunction = None
+
+        act1 = SCons.Action.Action(actclass1([t1], [s1], env))
+        s = act1.strfunction([t1], [s1], env)
+        assert s == 'actclass1(["t1"], ["s1"])', s
+
+        act2 = SCons.Action.Action(actclass2([t1], [s1], env))
+        s = act2.strfunction([t1], [s1], env)
+        assert s == 'actclass2(["t1"], ["s1"])', s
+
+        act3 = SCons.Action.Action(actclass3([t1], [s1], env))
+        s = act3.strfunction([t1], [s1], env)
+        assert s == 'actclass3 on s1 to get t1', s
+
+        act4 = SCons.Action.Action(actclass4([t1], [s1], env))
+        s = act4.strfunction([t1], [s1], env)
+        assert s is None, s
+
+        act = SCons.Action.CommandAction("@foo bar")
+        s = act.strfunction([], [], env)
+        assert s == "", s
+
+        act = SCons.Action.CommandAction("@-foo bar")
+        s = act.strfunction([], [], env)
+        assert s == "", s
+
+        act = SCons.Action.CommandAction("-@foo bar")
+        s = act.strfunction([], [], env)
+        assert s == "", s
+
+        act = SCons.Action.CommandAction("-foo bar")
+        s = act.strfunction([], [], env)
+        assert s == "foo bar", s
+
+        act = SCons.Action.CommandAction("@ foo bar")
+        s = act.strfunction([], [], env)
+        assert s == "", s
+
+        act = SCons.Action.CommandAction("@- foo bar")
+        s = act.strfunction([], [], env)
+        assert s == "", s
+
+        act = SCons.Action.CommandAction("-@ foo bar")
+        s = act.strfunction([], [], env)
+        assert s == "", s
+
+        act = SCons.Action.CommandAction("- foo bar")
+        s = act.strfunction([], [], env)
+        assert s == "foo bar", s
 
     def test_execute(self):
         """Test execution of command Actions
@@ -551,53 +1032,53 @@ class CommandActionTestCase(unittest.TestCase):
         except AttributeError:
             env = Environment()
 
-        cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile)
+        cmd1 = r'%s %s %s xyzzy' % (_python_, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd1)
-        r = act([], [], env.Copy())
+        r = act([], [], env.Clone())
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: 'xyzzy'\n", c
 
-        cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
+        cmd2 = r'%s %s %s $TARGET' % (_python_, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd2)
-        r = act(DummyNode('foo'), [], env.Copy())
+        r = act(DummyNode('foo'), [], env.Clone())
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: 'foo'\n", c
 
-        cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
+        cmd3 = r'%s %s %s ${TARGETS}' % (_python_, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd3)
-        r = act(map(DummyNode, ['aaa', 'bbb']), [], env.Copy())
+        r = act(list(map(DummyNode, ['aaa', 'bbb'])), [], env.Clone())
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: 'aaa' 'bbb'\n", c
 
-        cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
+        cmd4 = r'%s %s %s $SOURCES' % (_python_, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd4)
-        r = act([], [DummyNode('one'), DummyNode('two')], env.Copy())
+        r = act([], [DummyNode('one'), DummyNode('two')], env.Clone())
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: 'one' 'two'\n", c
 
-        cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile)
+        cmd4 = r'%s %s %s ${SOURCES[:2]}' % (_python_, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd4)
         sources = [DummyNode('three'), DummyNode('four'), DummyNode('five')]
-        env2 = env.Copy()
+        env2 = env.Clone()
         r = act([], source = sources, env = env2)
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: 'three' 'four'\n", c
 
-        cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile)
+        cmd5 = r'%s %s %s $TARGET XYZZY' % (_python_, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd5)
         env5 = Environment()
-        if scons_env.has_key('ENV'):
+        if 'ENV' in scons_env:
             env5['ENV'] = scons_env['ENV']
             PATH = scons_env['ENV'].get('PATH', '')
         else:
@@ -610,7 +1091,7 @@ class CommandActionTestCase(unittest.TestCase):
         act = SCons.Action.CommandAction(cmd5)
         r = act(target = DummyNode('out5'),
                 source = [],
-                env = env.Copy(ENV = {'XYZZY' : 'xyzzy5',
+                env = env.Clone(ENV = {'XYZZY' : 'xyzzy5',
                                       'PATH' : PATH}))
         assert r == 0
         c = test.read(outfile, 'r')
@@ -626,12 +1107,12 @@ class CommandActionTestCase(unittest.TestCase):
             def get_subst_proxy(self):
                 return self
 
-        cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile)
+        cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (_python_, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd6)
         r = act(target = [Obj('111'), Obj('222')],
                         source = [Obj('333'), Obj('444'), Obj('555')],
-                        env = env.Copy())
+                        env = env.Clone())
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: '222' '111' '333' '444'\n", c
@@ -640,32 +1121,71 @@ class CommandActionTestCase(unittest.TestCase):
             # NT treats execs of directories and non-executable files
             # as "file not found" errors
             expect_nonexistent = 1
-            expect_nonexecutable = 1
+            expect_nonexecutable_file = 1
+            expect_nonexecutable_dir = 1
         elif sys.platform == 'cygwin':
             expect_nonexistent = 127
-            expect_nonexecutable = 127
+            # Newer cygwin seems to return 126 for following
+            expect_nonexecutable_file = 126
+            expect_nonexecutable_dir  = 127
         else:
             expect_nonexistent = 127
-            expect_nonexecutable = 126
+            expect_nonexecutable_file = 126
+            expect_nonexecutable_dir  = 126
 
         # Test that a nonexistent command returns 127
         act = SCons.Action.CommandAction(python + "_no_such_command_")
-        r = act([], [], env.Copy(out = outfile))
-        assert r == expect_nonexistent, "r == %d" % r
+        r = act([], [], env.Clone(out = outfile))
+        assert r.status == expect_nonexistent, r.status
 
         # Test that trying to execute a directory returns 126
         dir, tail = os.path.split(python)
         act = SCons.Action.CommandAction(dir)
-        r = act([], [], env.Copy(out = outfile))
-        assert r == expect_nonexecutable, "r == %d" % r
+        r = act([], [], env.Clone(out = outfile))
+        assert r.status == expect_nonexecutable_file, r.status
 
         # Test that trying to execute a non-executable file returns 126
         act = SCons.Action.CommandAction(outfile)
-        r = act([], [], env.Copy(out = outfile))
-        assert r == expect_nonexecutable, "r == %d" % r
+        r = act([], [], env.Clone(out = outfile))
+        assert r.status == expect_nonexecutable_dir, r.status
+
+        act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py))
+        r = act([], [], env)
+        assert r.status == 1, r.status
 
-    def test_pipe_execute(self):
+        act = SCons.Action.CommandAction('@%s %s 1' % (_python_, exit_py))
+        r = act([], [], env)
+        assert r.status == 1, r.status
+
+        act = SCons.Action.CommandAction('@-%s %s 1' % (_python_, exit_py))
+        r = act([], [], env)
+        assert r == 0, r
+
+        act = SCons.Action.CommandAction('-%s %s 1' % (_python_, exit_py))
+        r = act([], [], env)
+        assert r == 0, r
+
+        act = SCons.Action.CommandAction('@ %s %s 1' % (_python_, exit_py))
+        r = act([], [], env)
+        assert r.status == 1, r.status
+
+        act = SCons.Action.CommandAction('@- %s %s 1' % (_python_, exit_py))
+        r = act([], [], env)
+        assert r == 0, r
+
+        act = SCons.Action.CommandAction('- %s %s 1' % (_python_, exit_py))
+        r = act([], [], env)
+        assert r == 0, r
+
+    def _DO_NOT_EXECUTE_test_pipe_execute(self):
         """Test capturing piped output from an action
+
+        We used to have PIPE_BUILD support built right into
+        Action.execute() for the benefit of the SConf subsystem, but we've
+        moved that logic back into SConf itself.  We'll leave this code
+        here, just in case we ever want to resurrect this functionality
+        in the future, but change the name of the test so it doesn't
+        get executed as part of the normal test suite.
         """
         pipe = open( pipe_file, "w" )
         self.env = Environment(ENV = {'ACTPY_PIPE' : '1'}, PIPE_BUILD = 1,
@@ -687,11 +1207,11 @@ class CommandActionTestCase(unittest.TestCase):
 
         # test redirection operators
         def test_redirect(self, redir, stdout_msg, stderr_msg):
-            cmd = r'%s %s %s xyzzy %s' % (python, act_py, outfile, redir)
-            # Write the output and error messages to files because Win32
-            # can't handle strings that are too big in its external
-            # environment (os.spawnve() returns EINVAL, "Invalid
-            # argument").
+            cmd = r'%s %s %s xyzzy %s' % (_python_, act_py, outfile, redir)
+            # Write the output and error messages to files because
+            # Windows can't handle strings that are too big in its
+            # external environment (os.spawnve() returns EINVAL,
+            # "Invalid argument").
             stdout_file = test.workpath('stdout_msg')
             stderr_file = test.workpath('stderr_msg')
             open(stdout_file, 'w').write(stdout_msg)
@@ -761,6 +1281,11 @@ class CommandActionTestCase(unittest.TestCase):
         a([], [], e)
         assert t.executed == [ 'xyzzy' ], t.executed
 
+        a = SCons.Action.CommandAction(["xyzzy"])
+        e = Environment(SPAWN = '$FUNC', FUNC = func)
+        a([], [], e)
+        assert t.executed == [ 'xyzzy' ], t.executed
+
         a = SCons.Action.CommandAction(["xyzzy"])
         e = Environment(SPAWN = func, SHELL = 'fake shell')
         a([], [], e)
@@ -791,7 +1316,7 @@ class CommandActionTestCase(unittest.TestCase):
         # Make sure that CommandActions use an Environment's
         # subst_target_source() method for substitution.
         class SpecialEnvironment(Environment):
-            def subst_target_source(self, strSubst, raw=0, target=[], source=[], dict=None):
+            def subst_target_source(self, strSubst, raw=0, target=[], source=[]):
                 return 'subst_target_source: ' + strSubst
 
         c = a.get_contents(target=DummyNode('ttt'), source = DummyNode('sss'),
@@ -808,21 +1333,17 @@ class CommandActionTestCase(unittest.TestCase):
         # that scheme, then all of the '__t1__' and '__s6__' file names
         # in the asserts below would change to 't1' and 's6' and the
         # like.
-        t = map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6'])
-        s = map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6'])
+        t = list(map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6']))
+        s = list(map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6']))
         env = Environment()
 
         a = SCons.Action.CommandAction(["$TARGET"])
         c = a.get_contents(target=t, source=s, env=env)
         assert c == "t1", c
-        c = a.get_contents(target=t, source=s, env=env, dict={})
-        assert c == "", c
 
         a = SCons.Action.CommandAction(["$TARGETS"])
         c = a.get_contents(target=t, source=s, env=env)
         assert c == "t1 t2 t3 t4 t5 t6", c
-        c = a.get_contents(target=t, source=s, env=env, dict={})
-        assert c == "", c
 
         a = SCons.Action.CommandAction(["${TARGETS[2]}"])
         c = a.get_contents(target=t, source=s, env=env)
@@ -835,14 +1356,10 @@ class CommandActionTestCase(unittest.TestCase):
         a = SCons.Action.CommandAction(["$SOURCE"])
         c = a.get_contents(target=t, source=s, env=env)
         assert c == "s1", c
-        c = a.get_contents(target=t, source=s, env=env, dict={})
-        assert c == "", c
 
         a = SCons.Action.CommandAction(["$SOURCES"])
         c = a.get_contents(target=t, source=s, env=env)
         assert c == "s1 s2 s3 s4 s5 s6", c
-        c = a.get_contents(target=t, source=s, env=env, dict={})
-        assert c == "", c
 
         a = SCons.Action.CommandAction(["${SOURCES[2]}"])
         c = a.get_contents(target=t, source=s, env=env)
@@ -854,20 +1371,32 @@ class CommandActionTestCase(unittest.TestCase):
 
 class CommandGeneratorActionTestCase(unittest.TestCase):
 
+    def factory(self, act, **kw):
+        """Pass any keywords as a dict"""
+        return SCons.Action.CommandGeneratorAction(act, kw)
+
     def test___init__(self):
         """Test creation of a command generator Action
         """
         def f(target, source, env):
             pass
-        a = SCons.Action.CommandGeneratorAction(f)
+        a = self.factory(f)
         assert a.generator == f
 
     def test___str__(self):
         """Test the pre-substitution strings for command generator Actions
         """
         def f(target, source, env, for_signature, self=self):
+
+            # See if "env" is really a construction environment (or
+            # looks like one) by accessing the FindIxes attribute.
+            # (The Tool/mingw.py module has a generator that uses this,
+            # and the __str__() method used to cause problems by passing
+            # us a regular dictionary as a fallback.)
+
+            env.FindIxes
             return "FOO"
-        a = SCons.Action.CommandGeneratorAction(f)
+        a = self.factory(f)
         s = str(a)
         assert s == 'FOO', s
 
@@ -878,25 +1407,12 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
             dummy = env['dummy']
             self.dummy = dummy
             return "$FOO $TARGET $SOURCE $TARGETS $SOURCES"
-        a = SCons.Action.CommandGeneratorAction(f)
+        a = self.factory(f)
         self.dummy = 0
         s = a.genstring([], [], env=Environment(FOO='xyzzy', dummy=1))
         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 test_execute(self):
         """Test executing a command generator Action
         """
@@ -918,7 +1434,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
             self.cmd.append(cmd)
             self.args.append(args)
 
-        a = SCons.Action.CommandGeneratorAction(f)
+        a = self.factory(f)
         self.dummy = 0
         self.cmd = []
         self.args = []
@@ -929,7 +1445,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
         assert self.cmd == ['foo', 'bar'], self.cmd
         assert self.args == [[ 'foo', 'baz' ], [ 'bar', 'ack' ]], self.args
 
-        b = SCons.Action.CommandGeneratorAction(f2)
+        b = self.factory(f2)
         self.dummy = 0
         b(target=[], source=[], env=Environment(foo =  'bar',
                                                         dummy =  2 ))
@@ -946,7 +1462,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
                 return self
         def f3(target, source, env, for_signature):
             return ''
-        c = SCons.Action.CommandGeneratorAction(f3)
+        c = self.factory(f3)
         c(target=[], source=DummyFile(self), env=Environment())
         assert self.rfile_called
 
@@ -966,11 +1482,62 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
 
         env = Environment(foo = 'FFF', bar =  'BBB',
                           ignore = 'foo', test=test)
-        a = SCons.Action.CommandGeneratorAction(f)
+        a = self.factory(f)
         c = a.get_contents(target=[], source=[], env=env)
         assert c == "guux FFF BBB test", c
-        c = a.get_contents(target=[], source=[], env=env, dict={})
-        assert c == "guux FFF BBB test", c
+
+    def test_get_contents_of_function_action(self):
+        """Test contents of a CommandGeneratorAction-generated FunctionAction
+        """
+
+        def LocalFunc():
+            pass
+
+        func_matches = [
+            "0,0,0,0,(),(),(d\000\000S),(),()",
+            "0,0,0,0,(),(),(d\x00\x00S),(),()",
+            ]
+        
+        meth_matches = [
+            "1,1,0,0,(),(),(d\000\000S),(),()",
+            "1,1,0,0,(),(),(d\x00\x00S),(),()",
+        ]
+
+        def f_global(target, source, env, for_signature):
+            return SCons.Action.Action(GlobalFunc)
+
+        # TODO(1.5):
+        #def f_local(target, source, env, for_signature):
+        def f_local(target, source, env, for_signature, LocalFunc=LocalFunc):
+            return SCons.Action.Action(LocalFunc)
+
+        env = Environment(XYZ = 'foo')
+
+        a = self.factory(f_global)
+        c = a.get_contents(target=[], source=[], env=env)
+        assert c in func_matches, repr(c)
+
+        a = self.factory(f_local)
+        c = a.get_contents(target=[], source=[], env=env)
+        assert c in func_matches, repr(c)
+
+        def f_global(target, source, env, for_signature):
+            return SCons.Action.Action(GlobalFunc, varlist=['XYZ'])
+
+        # TODO(1.5):
+        #def f_local(target, source, env, for_signature):
+        def f_local(target, source, env, for_signature, LocalFunc=LocalFunc):
+            return SCons.Action.Action(LocalFunc, varlist=['XYZ'])
+
+        matches_foo = [x + "foo" for x in func_matches]
+
+        a = self.factory(f_global)
+        c = a.get_contents(target=[], source=[], env=env)
+        assert c in matches_foo, repr(c)
+
+        a = self.factory(f_local)
+        c = a.get_contents(target=[], source=[], env=env)
+        assert c in matches_foo, repr(c)
 
 
 class FunctionActionTestCase(unittest.TestCase):
@@ -987,37 +1554,29 @@ class FunctionActionTestCase(unittest.TestCase):
         def func4():
             pass
 
-        a = SCons.Action.FunctionAction(func1)
+        a = SCons.Action.FunctionAction(func1, {})
         assert a.execfunction == func1, a.execfunction
-        assert isinstance(a.strfunction, types.FunctionType)
+        assert isinstance(a.strfunction, types.MethodType), type(a.strfunction)
 
-        a = SCons.Action.FunctionAction(func2, strfunction=func3)
+        a = SCons.Action.FunctionAction(func2, { 'strfunction' : func3 })
         assert a.execfunction == func2, a.execfunction
         assert a.strfunction == func3, a.strfunction
 
-        a = SCons.Action.FunctionAction(func3, func4)
-        assert a.execfunction == func3, a.execfunction
-        assert a.strfunction == func4, a.strfunction
-
-        a = SCons.Action.FunctionAction(func4, None)
-        assert a.execfunction == func4, a.execfunction
-        assert a.strfunction is None, a.strfunction
-
     def test___str__(self):
         """Test the __str__() method for function Actions
         """
         def func1():
             pass
-        a = SCons.Action.FunctionAction(func1)
+        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())
+        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
@@ -1030,7 +1589,7 @@ class FunctionActionTestCase(unittest.TestCase):
             s.source=source
             assert env.subst("$BAR") == 'foo bar', env.subst("$BAR")
             return 0
-        a = SCons.Action.FunctionAction(f)
+        a = SCons.Action.FunctionAction(f, {})
         a(target=1, source=2, env=Environment(BAR = 'foo bar',
                                                       s = self))
         assert self.inc == 1, self.inc
@@ -1046,14 +1605,11 @@ class FunctionActionTestCase(unittest.TestCase):
                 open(t, 'w').write("function1\n")
             return 1
 
-        act = SCons.Action.FunctionAction(function1)
-        r = None
-        try:
-            r = act(target = [outfile, outfile2], source=[], env=Environment())
-        except SCons.Errors.BuildError:
-            pass
-        assert r == 1
-        assert count == 1
+        act = SCons.Action.FunctionAction(function1, {})
+        r = act(target = [outfile, outfile2], source=[], env=Environment())
+        assert r.status == 1, r.status
+
+        assert count == 1, count
         c = test.read(outfile, 'r')
         assert c == "function1\n", c
         c = test.read(outfile2, 'r')
@@ -1063,9 +1619,9 @@ class FunctionActionTestCase(unittest.TestCase):
             def __init__(self, target, source, env):
                 open(env['out'], 'w').write("class1a\n")
 
-        act = SCons.Action.FunctionAction(class1a)
+        act = SCons.Action.FunctionAction(class1a, {})
         r = act([], [], Environment(out = outfile))
-        assert r.__class__ == class1a
+        assert isinstance(r.status, class1a), r.status
         c = test.read(outfile, 'r')
         assert c == "class1a\n", c
 
@@ -1074,19 +1630,20 @@ class FunctionActionTestCase(unittest.TestCase):
                 open(env['out'], 'w').write("class1b\n")
                 return 2
 
-        act = SCons.Action.FunctionAction(class1b())
+        act = SCons.Action.FunctionAction(class1b(), {})
         r = act([], [], Environment(out = outfile))
-        assert r == 2
+        assert r.status == 2, r.status
         c = test.read(outfile, 'r')
         assert c == "class1b\n", c
 
-        def build_it(target, source, env, self=self):
+        def build_it(target, source, env, executor=None, self=self):
             self.build_it = 1
             return 0
-        def string_it(target, source, env, self=self):
+        def string_it(target, source, env, executor=None, self=self):
             self.string_it = 1
             return None
-        act = SCons.Action.FunctionAction(build_it, string_it)
+        act = SCons.Action.FunctionAction(build_it,
+                                          { 'strfunction' : string_it })
         r = act([], [], Environment())
         assert r == 0, r
         assert self.build_it
@@ -1096,34 +1653,82 @@ class FunctionActionTestCase(unittest.TestCase):
         """Test fetching the contents of a function Action
         """
 
-        a = SCons.Action.FunctionAction(GlobalFunc)
+        def LocalFunc():
+            pass
 
-        matches = [
-            "\177\036\000\177\037\000d\000\000S",
-            "d\x00\x00S",
+        func_matches = [
+            "0,0,0,0,(),(),(d\000\000S),(),()",
+            "0,0,0,0,(),(),(d\x00\x00S),(),()",
+            ]
+        
+        meth_matches = [
+            "1,1,0,0,(),(),(d\000\000S),(),()",
+            "1,1,0,0,(),(),(d\x00\x00S),(),()",
         ]
 
+        def factory(act, **kw):
+            return SCons.Action.FunctionAction(act, kw)
+
+        a = factory(GlobalFunc)
         c = a.get_contents(target=[], source=[], env=Environment())
-        assert c in matches, repr(c)
-        c = a.get_contents(target=[], source=[], env=Environment(), dict={})
-        assert c in matches, repr(c)
+        assert c in func_matches, repr(c)
 
-        a = SCons.Action.FunctionAction(GlobalFunc, varlist=['XYZ'])
+        a = factory(LocalFunc)
+        c = a.get_contents(target=[], source=[], env=Environment())
+        assert c in func_matches, repr(c)
 
-        matches_foo = map(lambda x: x + "foo", matches)
+        matches_foo = [x + "foo" for x in func_matches]
 
+        a = factory(GlobalFunc, varlist=['XYZ'])
         c = a.get_contents(target=[], source=[], env=Environment())
-        assert c in matches, repr(c)
-        c = a.get_contents(target=[], source=[], env=Environment(XYZ = 'foo'))
+        assert c in func_matches, repr(c)
+        c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo'))
+        assert c in matches_foo, repr(c)
+
+        ##TODO: is this set of tests still needed?
+        # Make sure a bare string varlist works
+        a = factory(GlobalFunc, varlist='XYZ')
+        c = a.get_contents(target=[], source=[], env=Environment())
+        assert c in func_matches, repr(c)
+        c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo'))
         assert c in matches_foo, repr(c)
 
         class Foo:
-            def get_contents(self, target, source, env, dict=None):
+            def get_contents(self, target, source, env):
                 return 'xyzzy'
-        a = SCons.Action.FunctionAction(Foo())
+        a = factory(Foo())
         c = a.get_contents(target=[], source=[], env=Environment())
         assert c == 'xyzzy', repr(c)
 
+        class LocalClass:
+            def LocalMethod(self):
+                pass
+        lc = LocalClass()
+        a = factory(lc.LocalMethod)
+        c = a.get_contents(target=[], source=[], env=Environment())
+        assert c in meth_matches, repr(c)
+
+    def test_strfunction(self):
+        """Test the FunctionAction.strfunction() method
+        """
+        def func():
+            pass
+
+        def factory(act, **kw):
+            return SCons.Action.FunctionAction(act, kw)
+
+        a = factory(func)
+        s = a.strfunction(target=[], source=[], env=Environment())
+        assert s == 'func([], [])', s
+
+        a = factory(func, strfunction=None)
+        s = a.strfunction(target=[], source=[], env=Environment())
+        assert s is None, s
+
+        a = factory(func, cmdstr='function')
+        s = a.strfunction(target=[], source=[], env=Environment())
+        assert s == 'function', s
+
 class ListActionTestCase(unittest.TestCase):
 
     def test___init__(self):
@@ -1137,19 +1742,6 @@ class ListActionTestCase(unittest.TestCase):
         assert isinstance(a.list[2], SCons.Action.ListAction)
         assert a.list[2].list[0].cmd_list == 'y'
 
-    def test_get_actions(self):
-        """Test the get_actions() method for ListActions
-        """
-        a = SCons.Action.ListAction(["x", "y"])
-        l = a.get_actions()
-        assert len(l) == 2, l
-        assert isinstance(l[0], SCons.Action.CommandAction), l[0]
-        g = l[0].get_actions()
-        assert g == [l[0]], g
-        assert isinstance(l[1], SCons.Action.CommandAction), l[1]
-        g = l[1].get_actions()
-        assert g == [l[1]], g
-
     def test___str__(self):
         """Test the __str__() method for a list of subsidiary Actions
         """
@@ -1159,29 +1751,19 @@ class ListActionTestCase(unittest.TestCase):
             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
         """
         def f(target,source,env):
             pass
-        def g(target,source,env):
-            pass
+        def g(target,source,env,for_signature):
+            return 'generated %s %s' % (target[0], source[0])
+        g = SCons.Action.Action(g, generator=1)
         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
+        s = a.genstring(['foo.x'], ['bar.y'], Environment())
+        assert s == "f(target, source, env)\ngenerated foo.x bar.y\nXXX\nf(target, source, env)", s
 
     def test_execute(self):
         """Test executing a list of subsidiary Actions
@@ -1194,7 +1776,7 @@ class ListActionTestCase(unittest.TestCase):
         a([], [], Environment(s = self))
         assert self.inc == 3, self.inc
 
-        cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile)
+        cmd2 = r'%s %s %s syzygy' % (_python_, act_py, outfile)
 
         def function2(target, source, env):
             open(env['out'], 'a').write("function2\n")
@@ -1210,7 +1792,7 @@ class ListActionTestCase(unittest.TestCase):
                 open(env['out'], 'a').write("class2b\n")
         act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b])
         r = act([], [], Environment(out = outfile))
-        assert r.__class__ == class2b
+        assert isinstance(r.status, class2b), r.status
         c = test.read(outfile, 'r')
         assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
 
@@ -1223,38 +1805,26 @@ class ListActionTestCase(unittest.TestCase):
             s.foo=1
             return "y"
         a = SCons.Action.ListAction(["x",
-                                     SCons.Action.CommandGenerator(gen),
+                                     SCons.Action.Action(gen, generator=1),
                                      "z"])
         c = a.get_contents(target=[], source=[], env=Environment(s = self))
         assert self.foo==1, self.foo
         assert c == "xyz", c
-        c = a.get_contents(target=[], source=[], env=Environment(s = self), dict={})
-        assert self.foo==1, self.foo
-        assert c == "xyz", c
 
 class LazyActionTestCase(unittest.TestCase):
     def test___init__(self):
         """Test creation of a lazy-evaluation Action
         """
-        # Environment variable references should create a special
-        # type of CommandGeneratorAction that lazily evaluates the
-        # variable.
+        # Environment variable references should create a special type
+        # of LazyAction that lazily evaluates the variable for whether
+        # it's a string or something else before doing anything.
         a9 = SCons.Action.Action('$FOO')
-        assert isinstance(a9, SCons.Action.CommandGeneratorAction), a9
-        assert a9.generator.var == 'FOO', a9.generator.var
+        assert isinstance(a9, SCons.Action.LazyAction), a9
+        assert a9.var == 'FOO', a9.var
 
         a10 = SCons.Action.Action('${FOO}')
-        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
+        assert isinstance(a10, SCons.Action.LazyAction), a10
+        assert a10.var == 'FOO', a10.var
 
     def test_genstring(self):
         """Test the lazy-evaluation Action genstring() method
@@ -1262,8 +1832,12 @@ class LazyActionTestCase(unittest.TestCase):
         def f(target, source, env):
             pass
         a = SCons.Action.Action('$BAR')
-        s = a.genstring([], [], env=Environment(BAR=f, s=self))
-        assert s == "f(env, target, source)", s
+        env1 = Environment(BAR=f, s=self)
+        env2 = Environment(BAR='xxx', s=self)
+        s = a.genstring([], [], env=env1)
+        assert s == "f(target, source, env)", s
+        s = a.genstring([], [], env=env2)
+        assert s == 'xxx', s
 
     def test_execute(self):
         """Test executing a lazy-evaluation Action
@@ -1275,6 +1849,10 @@ class LazyActionTestCase(unittest.TestCase):
         a = SCons.Action.Action('$BAR')
         a([], [], env=Environment(BAR = f, s = self))
         assert self.test == 1, self.test
+        cmd = r'%s %s %s lazy' % (_python_, act_py, outfile)
+        a([], [], env=Environment(BAR = cmd, s = self))
+        c = test.read(outfile, 'r')
+        assert c == "act.py: 'lazy'\n", c
 
     def test_get_contents(self):
         """Test fetching the contents of a lazy-evaluation Action
@@ -1283,8 +1861,47 @@ class LazyActionTestCase(unittest.TestCase):
         env = Environment(FOO = [["This", "is", "a", "test"]])
         c = a.get_contents(target=[], source=[], env=env)
         assert c == "This is a test", c
-        c = a.get_contents(target=[], source=[], env=env, dict={})
-        assert c == "This is a test", c
+
+    def test_get_contents_of_function_action(self):
+        """Test fetching the contents of a lazy-evaluation FunctionAction
+        """
+
+        def LocalFunc():
+            pass
+
+        func_matches = [
+            "0,0,0,0,(),(),(d\000\000S),(),()",
+            "0,0,0,0,(),(),(d\x00\x00S),(),()",
+            ]
+        
+        meth_matches = [
+            "1,1,0,0,(),(),(d\000\000S),(),()",
+            "1,1,0,0,(),(),(d\x00\x00S),(),()",
+        ]
+
+        def factory(act, **kw):
+            return SCons.Action.FunctionAction(act, kw)
+
+
+        a = SCons.Action.Action("${FOO}")
+
+        env = Environment(FOO = factory(GlobalFunc))
+        c = a.get_contents(target=[], source=[], env=env)
+        assert c in func_matches, repr(c)
+
+        env = Environment(FOO = factory(LocalFunc))
+        c = a.get_contents(target=[], source=[], env=env)
+        assert c in func_matches, repr(c)
+
+        matches_foo = [x + "foo" for x in func_matches]
+
+        env = Environment(FOO = factory(GlobalFunc, varlist=['XYZ']))
+        c = a.get_contents(target=[], source=[], env=env)
+        assert c in func_matches, repr(c)
+
+        env['XYZ'] = 'foo'
+        c = a.get_contents(target=[], source=[], env=env)
+        assert c in matches_foo, repr(c)
 
 class ActionCallerTestCase(unittest.TestCase):
     def test___init__(self):
@@ -1299,8 +1916,11 @@ class ActionCallerTestCase(unittest.TestCase):
         def strfunc():
             pass
 
+        def LocalFunc():
+            pass
+
         matches = [
-            "\177\036\000\177\037\000d\000\000S",
+            "d\000\000S",
             "d\x00\x00S"
         ]
 
@@ -1309,16 +1929,30 @@ class ActionCallerTestCase(unittest.TestCase):
         c = ac.get_contents([], [], Environment())
         assert c in matches, repr(c)
 
+        af = SCons.Action.ActionFactory(LocalFunc, strfunc)
+        ac = SCons.Action.ActionCaller(af, [], {})
+        c = ac.get_contents([], [], Environment())
+        assert c in matches, repr(c)
+
         matches = [
-            '\177"\000\177#\000d\000\000S',
+            'd\000\000S',
             "d\x00\x00S"
         ]
 
+        class LocalActFunc:
+            def __call__(self):
+                pass
+
         af = SCons.Action.ActionFactory(GlobalActFunc(), strfunc)
         ac = SCons.Action.ActionCaller(af, [], {})
         c = ac.get_contents([], [], Environment())
         assert c in matches, repr(c)
 
+        af = SCons.Action.ActionFactory(LocalActFunc(), strfunc)
+        ac = SCons.Action.ActionCaller(af, [], {})
+        c = ac.get_contents([], [], Environment())
+        assert c in matches, repr(c)
+
         matches = [
             "<built-in function str>",
             "<type 'str'>",
@@ -1338,33 +1972,41 @@ class ActionCallerTestCase(unittest.TestCase):
         def strfunc(a1, a2, a3):
             pass
 
+        e = Environment(FOO = 2, BAR = 5)
+
         af = SCons.Action.ActionFactory(actfunc, strfunc)
-        ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3], {})
-        ac([], [], Environment(FOO = 2))
-        assert actfunc_args == [1, '2', 3], actfunc_args
+        ac = SCons.Action.ActionCaller(af, ['$__env__', '$FOO', 3], {})
+        ac([], [], e)
+        assert actfunc_args[0] is e, actfunc_args
+        assert actfunc_args[1] == '2', actfunc_args
+        assert actfunc_args[2] == 3, actfunc_args
+        del actfunc_args[:]
 
+        ac = SCons.Action.ActionCaller(af, [], {'a3' : '$__env__', 'a2' : '$BAR', 'a1' : 4})
+        ac([], [], e)
+        assert actfunc_args[0] == 4, actfunc_args
+        assert actfunc_args[1] == '5', actfunc_args
+        assert actfunc_args[2] is e, actfunc_args
         del actfunc_args[:]
-        ac = SCons.Action.ActionCaller(af, [], {'a3' : 6, 'a2' : '$BAR', 'a1' : 4})
-        ac([], [], Environment(BAR = 5))
-        assert actfunc_args == [4, '5', 6], actfunc_args
 
     def test_strfunction(self):
         """Test calling the ActionCaller strfunction() method"""
         strfunc_args = []
-        def actfunc(a1, a2, a3):
+        def actfunc(a1, a2, a3, a4):
             pass
-        def strfunc(a1, a2, a3, args=strfunc_args):
-            args.extend([a1, a2, a3])
+        def strfunc(a1, a2, a3, a4, args=strfunc_args):
+            args.extend([a1, a2, a3, a4])
 
         af = SCons.Action.ActionFactory(actfunc, strfunc)
-        ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3], {})
-        ac.strfunction([], [], Environment(FOO = 2))
-        assert strfunc_args == [1, '2', 3], strfunc_args
+        ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3, '$WS'], {})
+        ac.strfunction([], [], Environment(FOO = 2, WS='white   space'))
+        assert strfunc_args == [1, '2', 3, 'white   space'], strfunc_args
 
         del strfunc_args[:]
-        ac = SCons.Action.ActionCaller(af, [], {'a3' : 6, 'a2' : '$BAR', 'a1' : 4})
-        ac.strfunction([], [], Environment(BAR = 5))
-        assert strfunc_args == [4, '5', 6], strfunc_args
+        d = {'a3' : 6, 'a2' : '$BAR', 'a1' : 4, 'a4' : '$WS'}
+        ac = SCons.Action.ActionCaller(af, [], d)
+        ac.strfunction([], [], Environment(BAR = 5, WS='w   s'))
+        assert strfunc_args == [4, '5', 6, 'w   s'], strfunc_args
 
 class ActionFactoryTestCase(unittest.TestCase):
     def test___init__(self):
@@ -1391,19 +2033,76 @@ class ActionFactoryTestCase(unittest.TestCase):
         assert strfunc_args == [3, 6, 9], strfunc_args
 
 
+class ActionCompareTestCase(unittest.TestCase):
+
+    def test_1_solo_name(self):
+        """Test Lazy Cmd Generator Action get_name alone.
+
+        Basically ensures we can locate the builder, comparing it to
+        itself along the way."""
+        bar = SCons.Builder.Builder(action = {})
+        env = Environment( BUILDERS = {'BAR' : bar} )
+        name = bar.get_name(env)
+        assert name == 'BAR', name
+
+    def test_2_multi_name(self):
+        """Test LazyCmdGenerator Action get_name multi builders.
+
+        Ensure that we can compare builders (and thereby actions) to
+        each other safely."""
+        foo = SCons.Builder.Builder(action = '$FOO', suffix = '.foo')
+        bar = SCons.Builder.Builder(action = {})
+        assert foo != bar
+        assert foo.action != bar.action
+        env = Environment( BUILDERS = {'FOO' : foo,
+                                       'BAR' : bar} )
+        name = foo.get_name(env)
+        assert name == 'FOO', name
+        name = bar.get_name(env)
+        assert name == 'BAR', name
+
+    def test_3_dict_names(self):
+        """Test Action/Suffix dicts with get_name.
+
+        Verifies that Action/Suffix dictionaries work correctly,
+        especially two builders that can generate the same suffix,
+        where one of the builders has a suffix dictionary with a None
+        key."""
+        
+        foo = SCons.Builder.Builder(action = '$FOO', suffix = '.foo')
+        bar = SCons.Builder.Builder(action = {}, suffix={None:'.bar'})
+        bar.add_action('.cow', "$MOO")
+        dog = SCons.Builder.Builder(suffix = '.bar')
+        
+        env = Environment( BUILDERS = {'FOO' : foo,
+                                       'BAR' : bar,
+                                       'DOG' : dog} )
+        
+        assert foo.get_name(env) == 'FOO', foo.get_name(env)
+        assert bar.get_name(env) == 'BAR', bar.get_name(env)
+        assert dog.get_name(env) == 'DOG', dog.get_name(env)
+
+
 if __name__ == "__main__":
     suite = unittest.TestSuite()
-    tclasses = [ ActionTestCase,
-                 ActionBaseTestCase,
+    tclasses = [ _ActionActionTestCase,
+                 ActionTestCase,
                  CommandActionTestCase,
                  CommandGeneratorActionTestCase,
                  FunctionActionTestCase,
                  ListActionTestCase,
                  LazyActionTestCase,
                  ActionCallerTestCase,
-                 ActionFactoryTestCase ]
+                 ActionFactoryTestCase,
+                 ActionCompareTestCase ]
     for tclass in tclasses:
         names = unittest.getTestCaseNames(tclass, 'test_')
-        suite.addTests(map(tclass, names))
+        suite.addTests(list(map(tclass, names)))
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
         sys.exit(1)
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: