http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / EnvironmentTests.py
index bfd1262014717511efc9e1272c87e24bcb8b7c56..25404082a0b6311cf122018ae845e419ca39952e 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.compat
+
+import copy
 import os
 import os
-import string
+import StringIO
 import sys
 import TestCmd
 import unittest
 import sys
 import TestCmd
 import unittest
@@ -38,18 +41,16 @@ def diff_env(env1, env2):
     s2 = "env2 = {\n"
     d = {}
     for k in env1._dict.keys() + env2._dict.keys():
     s2 = "env2 = {\n"
     d = {}
     for k in env1._dict.keys() + env2._dict.keys():
-       d[k] = None
-    keys = d.keys()
-    keys.sort()
-    for k in keys:
-        if env1.has_key(k):
-           if env2.has_key(k):
+        d[k] = None
+    for k in sorted(d.keys()):
+        if k in env1:
+           if k in env2:
                if env1[k] != env2[k]:
                    s1 = s1 + "    " + repr(k) + " : " + repr(env1[k]) + "\n"
                    s2 = s2 + "    " + repr(k) + " : " + repr(env2[k]) + "\n"
            else:
                s1 = s1 + "    " + repr(k) + " : " + repr(env1[k]) + "\n"
                if env1[k] != env2[k]:
                    s1 = s1 + "    " + repr(k) + " : " + repr(env1[k]) + "\n"
                    s2 = s2 + "    " + repr(k) + " : " + repr(env2[k]) + "\n"
            else:
                s1 = s1 + "    " + repr(k) + " : " + repr(env1[k]) + "\n"
-        elif env2.has_key(k):
+        elif k in env2:
            s2 = s2 + "    " + repr(k) + " : " + repr(env2[k]) + "\n"
     s1 = s1 + "}\n"
     s2 = s2 + "}\n"
            s2 = s2 + "    " + repr(k) + " : " + repr(env2[k]) + "\n"
     s1 = s1 + "}\n"
     s2 = s2 + "}\n"
@@ -60,18 +61,16 @@ def diff_dict(d1, d2):
     s2 = "d2 = {\n"
     d = {}
     for k in d1.keys() + d2.keys():
     s2 = "d2 = {\n"
     d = {}
     for k in d1.keys() + d2.keys():
-       d[k] = None
-    keys = d.keys()
-    keys.sort()
-    for k in keys:
-        if d1.has_key(k):
-           if d2.has_key(k):
+        d[k] = None
+    for k in sorted(d.keys()):
+        if k in d1:
+           if k in d2:
                if d1[k] != d2[k]:
                    s1 = s1 + "    " + repr(k) + " : " + repr(d1[k]) + "\n"
                    s2 = s2 + "    " + repr(k) + " : " + repr(d2[k]) + "\n"
            else:
                s1 = s1 + "    " + repr(k) + " : " + repr(d1[k]) + "\n"
                if d1[k] != d2[k]:
                    s1 = s1 + "    " + repr(k) + " : " + repr(d1[k]) + "\n"
                    s2 = s2 + "    " + repr(k) + " : " + repr(d2[k]) + "\n"
            else:
                s1 = s1 + "    " + repr(k) + " : " + repr(d1[k]) + "\n"
-        elif env2.has_key(k):
+        elif k in env2:
            s2 = s2 + "    " + repr(k) + " : " + repr(d2[k]) + "\n"
     s1 = s1 + "}\n"
     s2 = s2 + "}\n"
            s2 = s2 + "    " + repr(k) + " : " + repr(d2[k]) + "\n"
     s1 = s1 + "}\n"
     s2 = s2 + "}\n"
@@ -80,7 +79,7 @@ def diff_dict(d1, d2):
 called_it = {}
 built_it = {}
 
 called_it = {}
 built_it = {}
 
-class Builder:
+class Builder(SCons.Builder.BuilderBase):
     """A dummy Builder class for testing purposes.  "Building"
     a target is simply setting a value in the dictionary.
     """
     """A dummy Builder class for testing purposes.  "Building"
     a target is simply setting a value in the dictionary.
     """
@@ -129,9 +128,13 @@ class Scanner:
 
 class CLVar(UserList.UserList):
     def __init__(self, seq):
 
 class CLVar(UserList.UserList):
     def __init__(self, seq):
-        if type(seq) == type(''):
-            seq = string.split(seq)
+        if isinstance(seq, str):
+            seq = seq.split()
         UserList.UserList.__init__(self, seq)
         UserList.UserList.__init__(self, seq)
+    def __add__(self, other):
+        return UserList.UserList.__add__(self, CLVar(other))
+    def __radd__(self, other):
+        return UserList.UserList.__radd__(self, CLVar(other))
     def __coerce__(self, other):
         return (self, CLVar(other))
 
     def __coerce__(self, other):
         return (self, CLVar(other))
 
@@ -147,7 +150,29 @@ class DummyNode:
     def get_subst_proxy(self):
         return self
 
     def get_subst_proxy(self):
         return self
 
-
+def test_tool( env ):
+    env['_F77INCFLAGS'] = '$( ${_concat(INCPREFIX, F77PATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)'
+
+class TestEnvironmentFixture:
+    def TestEnvironment(self, *args, **kw):
+        if not kw or 'tools' not in kw:
+            kw['tools'] = [test_tool]
+        default_keys = { 'CC' : 'cc',
+                         'CCFLAGS' : '-DNDEBUG',
+                         'ENV' : { 'TMP' : '/tmp' } }
+        for key, value in default_keys.items():
+            if key not in kw:
+                kw[key] = value
+        if 'BUILDERS' not in kw:
+            static_obj = SCons.Builder.Builder(action = {},
+                                               emitter = {},
+                                               suffix = '.o',
+                                               single_source = 1)
+            kw['BUILDERS'] = {'Object' : static_obj}
+            static_obj.add_action('.cpp', 'fake action')
+            
+        env = Environment(*args, **kw)
+        return env
 
 class SubstitutionTestCase(unittest.TestCase):
 
 
 class SubstitutionTestCase(unittest.TestCase):
 
@@ -155,7 +180,7 @@ class SubstitutionTestCase(unittest.TestCase):
         """Test initializing a SubstitutionEnvironment
         """
         env = SubstitutionEnvironment()
         """Test initializing a SubstitutionEnvironment
         """
         env = SubstitutionEnvironment()
-        assert not env.has_key('__env__')
+        assert '__env__' not in env
 
     def test___cmp__(self):
         """Test comparing SubstitutionEnvironments
 
     def test___cmp__(self):
         """Test comparing SubstitutionEnvironments
@@ -179,13 +204,13 @@ class SubstitutionTestCase(unittest.TestCase):
         assert env1 == env2
 
     def test___getitem__(self):
         assert env1 == env2
 
     def test___getitem__(self):
-        """Test deleting a variable from a SubstitutionEnvironment
+        """Test fetching a variable from a SubstitutionEnvironment
         """
         env = SubstitutionEnvironment(XXX = 'x')
         assert env['XXX'] == 'x', env['XXX']
 
     def test___setitem__(self):
         """
         env = SubstitutionEnvironment(XXX = 'x')
         assert env['XXX'] == 'x', env['XXX']
 
     def test___setitem__(self):
-        """Test deleting a variable from a SubstitutionEnvironment
+        """Test setting a variable in a SubstitutionEnvironment
         """
         env1 = SubstitutionEnvironment(XXX = 'x')
         env2 = SubstitutionEnvironment(XXX = 'x', YYY = 'y')
         """
         env1 = SubstitutionEnvironment(XXX = 'x')
         env2 = SubstitutionEnvironment(XXX = 'x', YYY = 'y')
@@ -203,8 +228,23 @@ class SubstitutionTestCase(unittest.TestCase):
         """Test the SubstitutionEnvironment has_key() method
         """
         env = SubstitutionEnvironment(XXX = 'x')
         """Test the SubstitutionEnvironment has_key() method
         """
         env = SubstitutionEnvironment(XXX = 'x')
-        assert env.has_key('XXX')
-        assert not env.has_key('YYY')
+        assert 'XXX' in env
+        assert 'YYY' not in env
+
+    def test_contains(self):
+        """Test the SubstitutionEnvironment __contains__() method
+        """
+        try:
+            'x' in {'x':1}
+        except TypeError:
+            # TODO(1.5)
+            # An early version of Python that doesn't support "in"
+            # on dictionaries.  Just pass the test.
+            pass
+        else:
+            env = SubstitutionEnvironment(XXX = 'x')
+            assert 'XXX' in env
+            assert not 'YYY' in env
 
     def test_items(self):
         """Test the SubstitutionEnvironment items() method
 
     def test_items(self):
         """Test the SubstitutionEnvironment items() method
@@ -221,7 +261,7 @@ class SubstitutionTestCase(unittest.TestCase):
         class X(SCons.Node.Node):
             pass
         def Factory(name, directory = None, create = 1, dict=dict, X=X):
         class X(SCons.Node.Node):
             pass
         def Factory(name, directory = None, create = 1, dict=dict, X=X):
-            if not dict.has_key(name):
+            if name not in dict:
                 dict[name] = X()
                 dict[name].name = name
             return dict[name]
                 dict[name] = X()
                 dict[name].name = name
             return dict[name]
@@ -231,8 +271,9 @@ class SubstitutionTestCase(unittest.TestCase):
         assert isinstance(nodes[0], X)
         assert nodes[0].name == "Util.py UtilTests.py"
 
         assert isinstance(nodes[0], X)
         assert nodes[0].name == "Util.py UtilTests.py"
 
-        import types
-        if hasattr(types, 'UnicodeType'):
+        try: unicode
+        except NameError: pass
+        else:
             code = """if 1:
                 nodes = env.arg2nodes(u"Util.py UtilTests.py", Factory)
                 assert len(nodes) == 1, nodes
             code = """if 1:
                 nodes = env.arg2nodes(u"Util.py UtilTests.py", Factory)
                 assert len(nodes) == 1, nodes
@@ -334,6 +375,22 @@ class SubstitutionTestCase(unittest.TestCase):
         assert not hasattr(nodes[1], 'bbbb'), nodes[0]
         assert nodes[1].c == 1, nodes[1]
 
         assert not hasattr(nodes[1], 'bbbb'), nodes[0]
         assert nodes[1].c == 1, nodes[1]
 
+    def test_arg2nodes_target_source(self):
+        """Test the arg2nodes method with target= and source= keywords
+        """
+        targets = [DummyNode('t1'), DummyNode('t2')]
+        sources = [DummyNode('s1'), DummyNode('s2')]
+        env = SubstitutionEnvironment()
+        nodes = env.arg2nodes(['${TARGET}-a',
+                               '${SOURCE}-b',
+                               '${TARGETS[1]}-c',
+                               '${SOURCES[1]}-d'],
+                              DummyNode,
+                              target=targets,
+                              source=sources)
+        names = [n.name for n in nodes]
+        assert names == ['t1-a', 's1-b', 't2-c', 's2-d'], names
+
     def test_gvars(self):
         """Test the base class gvars() method"""
         env = SubstitutionEnvironment()
     def test_gvars(self):
         """Test the base class gvars() method"""
         env = SubstitutionEnvironment()
@@ -366,6 +423,16 @@ class SubstitutionTestCase(unittest.TestCase):
         mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
         assert mystr == "c cA cB c", mystr
 
         mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
         assert mystr == "c cA cB c", mystr
 
+        # Lists:
+        env = SubstitutionEnvironment(AAA = ['a', 'aa', 'aaa'])
+        mystr = env.subst("$AAA")
+        assert mystr == "a aa aaa", mystr
+
+        # Tuples:
+        env = SubstitutionEnvironment(AAA = ('a', 'aa', 'aaa'))
+        mystr = env.subst("$AAA")
+        assert mystr == "a aa aaa", mystr
+
         t1 = DummyNode('t1')
         t2 = DummyNode('t2')
         s1 = DummyNode('s1')
         t1 = DummyNode('t1')
         t2 = DummyNode('t2')
         s1 = DummyNode('s1')
@@ -493,9 +560,13 @@ class SubstitutionTestCase(unittest.TestCase):
                 return self.val
 
         class MyObj:
                 return self.val
 
         class MyObj:
-            pass
+            def get(self):
+                return self
 
 
-        env = SubstitutionEnvironment(FOO='foo', BAR='bar', PROXY=MyProxy('my1'))
+        env = SubstitutionEnvironment(FOO='foo',
+                                      BAR='bar',
+                                      LIST=['one', 'two'],
+                                      PROXY=MyProxy('my1'))
 
         r = env.subst_path('$FOO')
         assert r == ['foo'], r
 
         r = env.subst_path('$FOO')
         assert r == ['foo'], r
@@ -503,14 +574,17 @@ class SubstitutionTestCase(unittest.TestCase):
         r = env.subst_path(['$FOO', 'xxx', '$BAR'])
         assert r == ['foo', 'xxx', 'bar'], r
 
         r = env.subst_path(['$FOO', 'xxx', '$BAR'])
         assert r == ['foo', 'xxx', 'bar'], r
 
+        r = env.subst_path(['$FOO', '$LIST', '$BAR'])
+        assert list(map(str, r)) == ['foo', 'one two', 'bar'], r
+
         r = env.subst_path(['$FOO', '$TARGET', '$SOURCE', '$BAR'])
         assert r == ['foo', '', '', 'bar'], r
 
         r = env.subst_path(['$FOO', '$TARGET', '$BAR'], target=MyNode('ttt'))
         r = env.subst_path(['$FOO', '$TARGET', '$SOURCE', '$BAR'])
         assert r == ['foo', '', '', 'bar'], r
 
         r = env.subst_path(['$FOO', '$TARGET', '$BAR'], target=MyNode('ttt'))
-        assert map(str, r) == ['foo', 'ttt', 'bar'], r
+        assert list(map(str, r)) == ['foo', 'ttt', 'bar'], r
 
         r = env.subst_path(['$FOO', '$SOURCE', '$BAR'], source=MyNode('sss'))
 
         r = env.subst_path(['$FOO', '$SOURCE', '$BAR'], source=MyNode('sss'))
-        assert map(str, r) == ['foo', 'sss', 'bar'], r
+        assert list(map(str, r)) == ['foo', 'sss', 'bar'], r
 
         n = MyObj()
 
 
         n = MyObj()
 
@@ -527,13 +601,13 @@ class SubstitutionTestCase(unittest.TestCase):
                           BAR=StringableObj("bar"))
 
         r = env.subst_path([ "${FOO}/bar", "${BAR}/baz" ])
                           BAR=StringableObj("bar"))
 
         r = env.subst_path([ "${FOO}/bar", "${BAR}/baz" ])
-        assert r == [ "foo/bar", "bar/baz" ]
+        assert r == [ "foo/bar", "bar/baz" ], r
 
         r = env.subst_path([ "bar/${FOO}", "baz/${BAR}" ])
 
         r = env.subst_path([ "bar/${FOO}", "baz/${BAR}" ])
-        assert r == [ "bar/foo", "baz/bar" ]
+        assert r == [ "bar/foo", "baz/bar" ], r
 
         r = env.subst_path([ "bar/${FOO}/bar", "baz/${BAR}/baz" ])
 
         r = env.subst_path([ "bar/${FOO}/bar", "baz/${BAR}/baz" ])
-        assert r == [ "bar/foo/bar", "baz/bar/baz" ]
+        assert r == [ "bar/foo/bar", "baz/bar/baz" ], r
 
     def test_subst_target_source(self):
         """Test the base environment subst_target_source() method"""
 
     def test_subst_target_source(self):
         """Test the base environment subst_target_source() method"""
@@ -541,6 +615,128 @@ class SubstitutionTestCase(unittest.TestCase):
         mystr = env.subst_target_source("$AAA ${AAA}A $BBBB $BBB")
         assert mystr == "a aA b", mystr
 
         mystr = env.subst_target_source("$AAA ${AAA}A $BBBB $BBB")
         assert mystr == "a aA b", mystr
 
+    def test_backtick(self):
+        """Test the backtick() method for capturing command output"""
+        env = SubstitutionEnvironment()
+
+        test = TestCmd.TestCmd(workdir = '')
+        test.write('stdout.py', """\
+import sys
+sys.stdout.write('this came from stdout.py\\n')
+sys.exit(0)
+""")
+        test.write('stderr.py', """\
+import sys
+sys.stderr.write('this came from stderr.py\\n')
+sys.exit(0)
+""")
+        test.write('fail.py', """\
+import sys
+sys.exit(1)
+""")
+        test.write('echo.py', """\
+import os, sys
+sys.stdout.write(os.environ['ECHO'] + '\\n')
+sys.exit(0)
+""")
+
+        save_stderr = sys.stderr
+
+        python = '"' + sys.executable + '"'
+
+        try:
+            sys.stderr = StringIO.StringIO()
+            cmd = '%s %s' % (python, test.workpath('stdout.py'))
+            output = env.backtick(cmd)
+            errout = sys.stderr.getvalue()
+            assert output == 'this came from stdout.py\n', output
+            assert errout == '', errout
+
+            sys.stderr = StringIO.StringIO()
+            cmd = '%s %s' % (python, test.workpath('stderr.py'))
+            output = env.backtick(cmd)
+            errout = sys.stderr.getvalue()
+            assert output == '', output
+            assert errout == 'this came from stderr.py\n', errout
+
+            sys.stderr = StringIO.StringIO()
+            cmd = '%s %s' % (python, test.workpath('fail.py'))
+            try:
+                env.backtick(cmd)
+            except OSError, e:
+                assert str(e) == "'%s' exited 1" % cmd, str(e)
+            else:
+                self.fail("did not catch expected OSError")
+
+            sys.stderr = StringIO.StringIO()
+            cmd = '%s %s' % (python, test.workpath('echo.py'))
+            env['ENV'] = os.environ.copy()
+            env['ENV']['ECHO'] = 'this came from ECHO'
+            output = env.backtick(cmd)
+            errout = sys.stderr.getvalue()
+            assert output == 'this came from ECHO\n', output
+            assert errout == '', errout
+
+        finally:
+            sys.stderr = save_stderr
+
+    def test_AddMethod(self):
+        """Test the AddMethod() method"""
+        env = SubstitutionEnvironment(FOO = 'foo')
+
+        def func(self):
+            return 'func-' + self['FOO']
+
+        assert not hasattr(env, 'func')
+        env.AddMethod(func)
+        r = env.func()
+        assert r == 'func-foo', r
+
+        assert not hasattr(env, 'bar')
+        env.AddMethod(func, 'bar')
+        r = env.bar()
+        assert r == 'func-foo', r
+
+        def func2(self, arg=''):
+            return 'func2-' + self['FOO'] + arg
+
+        env.AddMethod(func2)
+        r = env.func2()
+        assert r == 'func2-foo', r
+        r = env.func2('-xxx')
+        assert r == 'func2-foo-xxx', r
+
+        env.AddMethod(func2, 'func')
+        r = env.func()
+        assert r == 'func2-foo', r
+        r = env.func('-yyy')
+        assert r == 'func2-foo-yyy', r
+
+        # Test that clones of clones correctly re-bind added methods.
+        env1 = Environment(FOO = '1')
+        env1.AddMethod(func2)
+        env2 = env1.Clone(FOO = '2')
+        env3 = env2.Clone(FOO = '3')
+        env4 = env3.Clone(FOO = '4')
+        r = env1.func2()
+        assert r == 'func2-1', r
+        r = env2.func2()
+        assert r == 'func2-2', r
+        r = env3.func2()
+        assert r == 'func2-3', r
+        r = env4.func2()
+        assert r == 'func2-4', r
+
+        # Test that clones don't re-bind an attribute that the user
+        env1 = Environment(FOO = '1')
+        env1.AddMethod(func2)
+        def replace_func2():
+            return 'replace_func2'
+        env1.func2 = replace_func2
+        env2 = env1.Clone(FOO = '2')
+        r = env2.func2()
+        assert r == 'replace_func2', r
+
     def test_Override(self):
         "Test overriding construction variables"
         env = SubstitutionEnvironment(ONE=1, TWO=2, THREE=3, FOUR=4)
     def test_Override(self):
         "Test overriding construction variables"
         env = SubstitutionEnvironment(ONE=1, TWO=2, THREE=3, FOUR=4)
@@ -566,9 +762,140 @@ class SubstitutionTestCase(unittest.TestCase):
         assert env2['ONE'] == "won", env2['ONE']
         assert env['ONE'] == 1, env['ONE']
 
         assert env2['ONE'] == "won", env2['ONE']
         assert env['ONE'] == 1, env['ONE']
 
+    def test_ParseFlags(self):
+        """Test the ParseFlags() method
+        """
+        env = SubstitutionEnvironment()
+
+        empty = {
+            'ASFLAGS'       : [],
+            'CFLAGS'        : [],
+            'CCFLAGS'       : [],
+            'CPPDEFINES'    : [],
+            'CPPFLAGS'      : [],
+            'CPPPATH'       : [],
+            'FRAMEWORKPATH' : [],
+            'FRAMEWORKS'    : [],
+            'LIBPATH'       : [],
+            'LIBS'          : [],
+            'LINKFLAGS'     : [],
+            'RPATH'         : [],
+        }
+
+        d = env.ParseFlags(None)
+        assert d == empty, d
+
+        d = env.ParseFlags('')
+        assert d == empty, d
+
+        d = env.ParseFlags([])
+        assert d == empty, d
+
+        s = "-I/usr/include/fum -I bar -X\n" + \
+            '-I"C:\\Program Files\\ASCEND\\include" ' + \
+            "-L/usr/fax -L foo -lxxx -l yyy " + \
+            '-L"C:\\Program Files\\ASCEND" -lascend ' + \
+            "-Wa,-as -Wl,-link " + \
+            "-Wl,-rpath=rpath1 " + \
+            "-Wl,-R,rpath2 " + \
+            "-Wl,-Rrpath3 " + \
+            "-Wp,-cpp " + \
+            "-std=c99 " + \
+            "-framework Carbon " + \
+            "-frameworkdir=fwd1 " + \
+            "-Ffwd2 " + \
+            "-F fwd3 " + \
+            "-pthread " + \
+            "-mno-cygwin -mwindows " + \
+            "-arch i386 -isysroot /tmp +DD64 " + \
+            "-DFOO -DBAR=value -D BAZ "
+
+        d = env.ParseFlags(s)
+
+        assert d['ASFLAGS'] == ['-as'], d['ASFLAGS']
+        assert d['CFLAGS']  == ['-std=c99']
+        assert d['CCFLAGS'] == ['-X', '-Wa,-as',
+                                  '-pthread', '-mno-cygwin',
+                                  ('-arch', 'i386'), ('-isysroot', '/tmp'),
+                                  '+DD64'], d['CCFLAGS']
+        assert d['CPPDEFINES'] == ['FOO', ['BAR', 'value'], 'BAZ'], d['CPPDEFINES']
+        assert d['CPPFLAGS'] == ['-Wp,-cpp'], d['CPPFLAGS']
+        assert d['CPPPATH'] == ['/usr/include/fum',
+                                'bar',
+                                'C:\\Program Files\\ASCEND\\include'], d['CPPPATH']
+        assert d['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], d['FRAMEWORKPATH']
+        assert d['FRAMEWORKS'] == ['Carbon'], d['FRAMEWORKS']
+        assert d['LIBPATH'] == ['/usr/fax',
+                                'foo',
+                                'C:\\Program Files\\ASCEND'], d['LIBPATH']
+        LIBS = list(map(str, d['LIBS']))
+        assert LIBS == ['xxx', 'yyy', 'ascend'], (d['LIBS'], LIBS)
+        assert d['LINKFLAGS'] == ['-Wl,-link', '-pthread',
+                                  '-mno-cygwin', '-mwindows',
+                                  ('-arch', 'i386'),
+                                  ('-isysroot', '/tmp'),
+                                  '+DD64'], d['LINKFLAGS']
+        assert d['RPATH'] == ['rpath1', 'rpath2', 'rpath3'], d['RPATH']
+
+
+    def test_MergeFlags(self):
+        """Test the MergeFlags() method
+        """
+        env = SubstitutionEnvironment()
+        env.MergeFlags('')
+        assert 'CCFLAGS' not in env, env['CCFLAGS']
+        env.MergeFlags('-X')
+        assert env['CCFLAGS'] == ['-X'], env['CCFLAGS']
+        env.MergeFlags('-X')
+        assert env['CCFLAGS'] == ['-X'], env['CCFLAGS']
 
 
+        env = SubstitutionEnvironment(CCFLAGS=None)
+        env.MergeFlags('-Y')
+        assert env['CCFLAGS'] == ['-Y'], env['CCFLAGS']
 
 
-class BaseTestCase(unittest.TestCase):
+        env = SubstitutionEnvironment()
+        env.MergeFlags({'A':['aaa'], 'B':['bbb']})
+        assert env['A'] == ['aaa'], env['A']
+        assert env['B'] == ['bbb'], env['B']
+
+#     def test_MergeShellPaths(self):
+#         """Test the MergeShellPaths() method
+#         """
+#         env = Environment()
+#         env.MergeShellPaths({})
+#         assert not env['ENV'].has_key('INCLUDE'), env['INCLUDE']
+#         env.MergeShellPaths({'INCLUDE': r'c:\Program Files\Stuff'})
+#         assert env['ENV']['INCLUDE'] == r'c:\Program Files\Stuff', env['ENV']['INCLUDE']
+#         env.MergeShellPaths({'INCLUDE': r'c:\Program Files\Stuff'})
+#         assert env['ENV']['INCLUDE'] == r'c:\Program Files\Stuff', env['ENV']['INCLUDE']
+#         env.MergeShellPaths({'INCLUDE': r'xyz'})
+#         assert env['ENV']['INCLUDE'] == r'xyz%sc:\Program Files\Stuff'%os.pathsep, env['ENV']['INCLUDE']
+
+#         env = Environment()
+#         env['ENV']['INCLUDE'] = 'xyz'
+#         env.MergeShellPaths({'INCLUDE':['c:/inc1', 'c:/inc2']} )
+#         assert env['ENV']['INCLUDE'] == r'c:/inc1%sc:/inc2%sxyz'%(os.pathsep, os.pathsep), env['ENV']['INCLUDE']
+
+#         # test prepend=0
+#         env = Environment()
+#         env.MergeShellPaths({'INCLUDE': r'c:\Program Files\Stuff'}, prepend=0)
+#         assert env['ENV']['INCLUDE'] == r'c:\Program Files\Stuff', env['ENV']['INCLUDE']
+#         env.MergeShellPaths({'INCLUDE': r'xyz'}, prepend=0)
+#         assert env['ENV']['INCLUDE'] == r'c:\Program Files\Stuff%sxyz'%os.pathsep, env['ENV']['INCLUDE']
+
+
+class BaseTestCase(unittest.TestCase,TestEnvironmentFixture):
+
+    reserved_variables = [
+        'CHANGED_SOURCES',
+        'CHANGED_TARGETS',
+        'SOURCE',
+        'SOURCES',
+        'TARGET',
+        'TARGETS',
+        'UNCHANGED_SOURCES',
+        'UNCHANGED_TARGETS',
+    ]
 
     def test___init__(self):
         """Test construction Environment creation
 
     def test___init__(self):
         """Test construction Environment creation
@@ -576,16 +903,34 @@ class BaseTestCase(unittest.TestCase):
         Create two with identical arguments and check that
         they compare the same.
         """
         Create two with identical arguments and check that
         they compare the same.
         """
-        env1 = Environment(XXX = 'x', YYY = 'y')
-        env2 = Environment(XXX = 'x', YYY = 'y')
+        env1 = self.TestEnvironment(XXX = 'x', YYY = 'y')
+        env2 = self.TestEnvironment(XXX = 'x', YYY = 'y')
         assert env1 == env2, diff_env(env1, env2)
 
         assert env1 == env2, diff_env(env1, env2)
 
-        assert not env1.has_key('__env__')
-        assert not env2.has_key('__env__')
+        assert '__env__' not in env1
+        assert '__env__' not in env2
+
+    def test_variables(self):
+        """Test that variables only get applied once."""
+        class FakeOptions:
+            def __init__(self, key, val):
+                self.calls = 0
+                self.key = key
+                self.val = val
+            def keys(self):
+                return [self.key]
+            def Update(self, env):
+                env[self.key] = self.val
+                self.calls = self.calls + 1
+
+        o = FakeOptions('AAA', 'fake_opt')
+        env = Environment(variables=o, AAA='keyword_arg')
+        assert o.calls == 1, o.calls
+        assert env['AAA'] == 'fake_opt', env['AAA']
 
     def test_get(self):
         """Test the get() method."""
 
     def test_get(self):
         """Test the get() method."""
-        env = Environment(aaa = 'AAA')
+        env = self.TestEnvironment(aaa = 'AAA')
 
         x = env.get('aaa')
         assert x == 'AAA', x
 
         x = env.get('aaa')
         assert x == 'AAA', x
@@ -609,53 +954,80 @@ class BaseTestCase(unittest.TestCase):
                                  'builder2' : b2 })
         called_it = {}
         env.builder1('in1')
                                  'builder2' : b2 })
         called_it = {}
         env.builder1('in1')
-        assert called_it['target'] == None, called_it
+        assert called_it['target'] is None, called_it
         assert called_it['source'] == ['in1'], called_it
 
         called_it = {}
         env.builder2(source = 'in2', xyzzy = 1)
         assert called_it['source'] == ['in1'], called_it
 
         called_it = {}
         env.builder2(source = 'in2', xyzzy = 1)
-        assert called_it['target'] == None, called_it
+        assert called_it['target'] is None, called_it
         assert called_it['source'] == ['in2'], called_it
         assert called_it['xyzzy'] == 1, called_it
 
         called_it = {}
         env.builder1(foo = 'bar')
         assert called_it['foo'] == 'bar', called_it
         assert called_it['source'] == ['in2'], called_it
         assert called_it['xyzzy'] == 1, called_it
 
         called_it = {}
         env.builder1(foo = 'bar')
         assert called_it['foo'] == 'bar', called_it
-        assert called_it['target'] == None, called_it
-        assert called_it['source'] == None, called_it
+        assert called_it['target'] is None, called_it
+        assert called_it['source'] is None, called_it
 
 
+    def test_BuilderWrapper_attributes(self):
+        """Test getting and setting of BuilderWrapper attributes
+        """
+        b1 = Builder()
+        b2 = Builder()
+        e1 = Environment()
+        e2 = Environment()
 
 
+        e1.Replace(BUILDERS = {'b' : b1})
+        bw = e1.b
 
 
-    def test_Builder_execs(self):
-       """Test Builder execution through different environments
+        assert bw.env is e1
+        bw.env = e2
+        assert bw.env is e2
 
 
-       One environment is initialized with a single
-       Builder object, one with a list of a single Builder
-       object, and one with a list of two Builder objects.
-       """
-       global built_it
+        assert bw.builder is b1
+        bw.builder = b2
+        assert bw.builder is b2
 
 
-       b1 = Builder()
-       b2 = Builder()
+        self.assertRaises(AttributeError, getattr, bw, 'foobar')
+        bw.foobar = 42
+        assert bw.foobar is 42
 
 
-       built_it = {}
+    # This unit test is currently disabled because we don't think the
+    # underlying method it tests (Environment.BuilderWrapper.execute())
+    # is necessary, but we're leaving the code here for now in case
+    # that's mistaken.
+    def _DO_NOT_test_Builder_execs(self):
+        """Test Builder execution through different environments
+
+        One environment is initialized with a single
+        Builder object, one with a list of a single Builder
+        object, and one with a list of two Builder objects.
+        """
+        global built_it
+
+        b1 = Builder()
+        b2 = Builder()
+
+        built_it = {}
         env3 = Environment()
         env3.Replace(BUILDERS = { 'builder1' : b1,
                                   'builder2' : b2 })
         env3 = Environment()
         env3.Replace(BUILDERS = { 'builder1' : b1,
                                   'builder2' : b2 })
-       env3.builder1.execute(target = 'out1')
-       env3.builder2.execute(target = 'out2')
-       env3.builder1.execute(target = 'out3')
-       assert built_it['out1']
-       assert built_it['out2']
-       assert built_it['out3']
+        env3.builder1.execute(target = 'out1')
+        env3.builder2.execute(target = 'out2')
+        env3.builder1.execute(target = 'out3')
+        assert built_it['out1']
+        assert built_it['out2']
+        assert built_it['out3']
 
 
-        env4 = env3.Copy()
-        assert env4.builder1.env is env4, "builder1.env (%s) == env3 (%s)?" % (env4.builder1.env, env3)
-        assert env4.builder2.env is env4, "builder2.env (%s) == env3 (%s)?" % (env4.builder1.env, env3)
+        env4 = env3.Clone()
+        assert env4.builder1.env is env4, "builder1.env (%s) == env3 (%s)?" % (
+env4.builder1.env, env3)
+        assert env4.builder2.env is env4, "builder2.env (%s) == env3 (%s)?" % (
+env4.builder1.env, env3)
 
         # Now test BUILDERS as a dictionary.
         built_it = {}
 
         # Now test BUILDERS as a dictionary.
         built_it = {}
-        env5 = Environment(BUILDERS={ 'foo' : b1 })
+        env5 = self.TestEnvironment(BUILDERS={ 'foo' : b1 })
         env5['BUILDERS']['bar'] = b2
         env5.foo.execute(target='out1')
         env5.bar.execute(target='out2')
         env5['BUILDERS']['bar'] = b2
         env5.foo.execute(target='out1')
         env5.bar.execute(target='out2')
@@ -671,6 +1043,8 @@ class BaseTestCase(unittest.TestCase):
         assert built_it['out1']
         assert built_it['out2']
 
         assert built_it['out1']
         assert built_it['out2']
 
+
+
     def test_Scanners(self):
         """Test setting SCANNERS in various ways
 
     def test_Scanners(self):
         """Test setting SCANNERS in various ways
 
@@ -683,16 +1057,17 @@ class BaseTestCase(unittest.TestCase):
         s1 = Scanner(name = 'scanner1', skeys = [".c", ".cc"])
         s2 = Scanner(name = 'scanner2', skeys = [".m4"])
         s3 = Scanner(name = 'scanner3', skeys = [".m4", ".m5"])
         s1 = Scanner(name = 'scanner1', skeys = [".c", ".cc"])
         s2 = Scanner(name = 'scanner2', skeys = [".m4"])
         s3 = Scanner(name = 'scanner3', skeys = [".m4", ".m5"])
+        s4 = Scanner(name = 'scanner4', skeys = [None])
 
 #        XXX Tests for scanner execution through different environments,
 #        XXX if we ever want to do that some day
 #        scanned_it = {}
 
 #        XXX Tests for scanner execution through different environments,
 #        XXX if we ever want to do that some day
 #        scanned_it = {}
-#        env1 = Environment(SCANNERS = s1)
+#        env1 = self.TestEnvironment(SCANNERS = s1)
 #        env1.scanner1(filename = 'out1')
 #        assert scanned_it['out1']
 #
 #        scanned_it = {}
 #        env1.scanner1(filename = 'out1')
 #        assert scanned_it['out1']
 #
 #        scanned_it = {}
-#        env2 = Environment(SCANNERS = [s1])
+#        env2 = self.TestEnvironment(SCANNERS = [s1])
 #        env1.scanner1(filename = 'out1')
 #        assert scanned_it['out1']
 #
 #        env1.scanner1(filename = 'out1')
 #        assert scanned_it['out1']
 #
@@ -708,64 +1083,126 @@ class BaseTestCase(unittest.TestCase):
 
         suffixes = [".c", ".cc", ".cxx", ".m4", ".m5"]
 
 
         suffixes = [".c", ".cc", ".cxx", ".m4", ".m5"]
 
-        env = Environment(SCANNERS = [])
-        s = map(env.get_scanner, suffixes)
+        env = Environment()
+        try: del env['SCANNERS']
+        except KeyError: pass
+        s = list(map(env.get_scanner, suffixes))
+        assert s == [None, None, None, None, None], s
+
+        env = self.TestEnvironment(SCANNERS = [])
+        s = list(map(env.get_scanner, suffixes))
         assert s == [None, None, None, None, None], s
 
         env.Replace(SCANNERS = [s1])
         assert s == [None, None, None, None, None], s
 
         env.Replace(SCANNERS = [s1])
-        s = map(env.get_scanner, suffixes)
+        s = list(map(env.get_scanner, suffixes))
         assert s == [s1, s1, None, None, None], s
 
         env.Append(SCANNERS = [s2])
         assert s == [s1, s1, None, None, None], s
 
         env.Append(SCANNERS = [s2])
-        s = map(env.get_scanner, suffixes)
+        s = list(map(env.get_scanner, suffixes))
         assert s == [s1, s1, None, s2, None], s
 
         env.AppendUnique(SCANNERS = [s3])
         assert s == [s1, s1, None, s2, None], s
 
         env.AppendUnique(SCANNERS = [s3])
-        s = map(env.get_scanner, suffixes)
+        s = list(map(env.get_scanner, suffixes))
         assert s == [s1, s1, None, s2, s3], s
 
         assert s == [s1, s1, None, s2, s3], s
 
-        env = env.Copy(SCANNERS = [s2])
-        s = map(env.get_scanner, suffixes)
+        env = env.Clone(SCANNERS = [s2])
+        s = list(map(env.get_scanner, suffixes))
         assert s == [None, None, None, s2, None], s
 
         env['SCANNERS'] = [s1]
         assert s == [None, None, None, s2, None], s
 
         env['SCANNERS'] = [s1]
-        s = map(env.get_scanner, suffixes)
+        s = list(map(env.get_scanner, suffixes))
         assert s == [s1, s1, None, None, None], s
 
         env.PrependUnique(SCANNERS = [s2, s1])
         assert s == [s1, s1, None, None, None], s
 
         env.PrependUnique(SCANNERS = [s2, s1])
-        s = map(env.get_scanner, suffixes)
+        s = list(map(env.get_scanner, suffixes))
         assert s == [s1, s1, None, s2, None], s
 
         env.Prepend(SCANNERS = [s3])
         assert s == [s1, s1, None, s2, None], s
 
         env.Prepend(SCANNERS = [s3])
-        s = map(env.get_scanner, suffixes)
+        s = list(map(env.get_scanner, suffixes))
         assert s == [s1, s1, None, s3, s3], s
 
         assert s == [s1, s1, None, s3, s3], s
 
+        # Verify behavior of case-insensitive suffix matches on Windows.
+        uc_suffixes = [_.upper() for _ in suffixes]
+
+        env = Environment(SCANNERS = [s1, s2, s3],
+                          PLATFORM = 'linux')
+
+        s = list(map(env.get_scanner, suffixes))
+        assert s == [s1, s1, None, s2, s3], s
+
+        s = list(map(env.get_scanner, uc_suffixes))
+        assert s == [None, None, None, None, None], s
+
+        env['PLATFORM'] = 'win32'
+
+        s = list(map(env.get_scanner, uc_suffixes))
+        assert s == [s1, s1, None, s2, s3], s
+
+        # Verify behavior for a scanner returning None (on Windows
+        # where we might try to perform case manipulation on None).
+        env.Replace(SCANNERS = [s4])
+        s = list(map(env.get_scanner, suffixes))
+        assert s == [None, None, None, None, None], s
+
     def test_ENV(self):
     def test_ENV(self):
-       """Test setting the external ENV in Environments
-       """
-       env = Environment()
-       assert env.Dictionary().has_key('ENV')
+        """Test setting the external ENV in Environments
+        """
+        env = Environment()
+        assert 'ENV' in env.Dictionary()
 
 
-       env = Environment(ENV = { 'PATH' : '/foo:/bar' })
-       assert env.Dictionary('ENV')['PATH'] == '/foo:/bar'
+        env = self.TestEnvironment(ENV = { 'PATH' : '/foo:/bar' })
+        assert env.Dictionary('ENV')['PATH'] == '/foo:/bar'
 
     def test_ReservedVariables(self):
 
     def test_ReservedVariables(self):
-        """Test generation of warnings when reserved variable names
-        are set in an environment."""
+        """Test warning generation when reserved variable names are set"""
+
+        reserved_variables = [
+            'CHANGED_SOURCES',
+            'CHANGED_TARGETS',
+            'SOURCE',
+            'SOURCES',
+            'TARGET',
+            'TARGETS',
+            'UNCHANGED_SOURCES',
+            'UNCHANGED_TARGETS',
+        ]
 
 
-        SCons.Warnings.enableWarningClass(SCons.Warnings.ReservedVariableWarning)
+        warning = SCons.Warnings.ReservedVariableWarning
+        SCons.Warnings.enableWarningClass(warning)
         old = SCons.Warnings.warningAsException(1)
 
         try:
             env4 = Environment()
         old = SCons.Warnings.warningAsException(1)
 
         try:
             env4 = Environment()
-            for kw in ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
+            for kw in self.reserved_variables:
                 exc_caught = None
                 try:
                     env4[kw] = 'xyzzy'
                 exc_caught = None
                 try:
                     env4[kw] = 'xyzzy'
-                except SCons.Warnings.ReservedVariableWarning:
+                except warning:
                     exc_caught = 1
                 assert exc_caught, "Did not catch ReservedVariableWarning for `%s'" % kw
                     exc_caught = 1
                 assert exc_caught, "Did not catch ReservedVariableWarning for `%s'" % kw
-                assert not env4.has_key(kw), "`%s' variable was incorrectly set" % kw
+                assert kw not in env4, "`%s' variable was incorrectly set" % kw
+        finally:
+            SCons.Warnings.warningAsException(old)
+
+    def test_FutureReservedVariables(self):
+        """Test warning generation when future reserved variable names are set"""
+
+        future_reserved_variables = []
+
+        warning = SCons.Warnings.FutureReservedVariableWarning
+        SCons.Warnings.enableWarningClass(warning)
+        old = SCons.Warnings.warningAsException(1)
+
+        try:
+            env4 = Environment()
+            for kw in future_reserved_variables:
+                exc_caught = None
+                try:
+                    env4[kw] = 'xyzzy'
+                except warning:
+                    exc_caught = 1
+                assert exc_caught, "Did not catch FutureReservedVariableWarning for `%s'" % kw
+                assert kw in env4, "`%s' variable was not set" % kw
         finally:
             SCons.Warnings.warningAsException(old)
 
         finally:
             SCons.Warnings.warningAsException(old)
 
@@ -788,128 +1225,91 @@ class BaseTestCase(unittest.TestCase):
     def test_autogenerate(dict):
         """Test autogenerating variables in a dictionary."""
 
     def test_autogenerate(dict):
         """Test autogenerating variables in a dictionary."""
 
-        def RDirs(pathlist):
-            return SCons.Node.FS.default_fs.Rsearchall(pathlist,
-                                                       clazz=SCons.Node.FS.Dir,
-                                                       must_exist=0,
-                                                       cwd=SCons.Node.FS.default_fs.Dir('xx'))
+        drive, p = os.path.splitdrive(os.getcwd())
+        def normalize_path(path, drive=drive):
+            if path[0] in '\\/':
+                path = drive + path
+            path = os.path.normpath(path)
+            drive, path = os.path.splitdrive(path)
+            return drive.lower() + path
 
 
-        env = Environment(LIBS = [ 'foo', 'bar', 'baz' ],
+        env = dict.TestEnvironment(LIBS = [ 'foo', 'bar', 'baz' ],
                           LIBLINKPREFIX = 'foo',
                           LIBLINKPREFIX = 'foo',
-                          LIBLINKSUFFIX = 'bar',
-                          RDirs=RDirs)
+                          LIBLINKSUFFIX = 'bar')
+
+        def RDirs(pathlist, fs=env.fs):
+            return fs.Dir('xx').Rfindalldirs(pathlist)
+
+        env['RDirs'] = RDirs
         flags = env.subst_list('$_LIBFLAGS', 1)[0]
         flags = env.subst_list('$_LIBFLAGS', 1)[0]
-        assert len(flags) == 3, flags
-        assert flags[0] == 'foobar', \
-               flags[0]
-        assert flags[1] == 'foobar', \
-               flags[1]
-        assert flags[2] == 'foobazbar', \
-               flags[2]
-
-        blat = SCons.Node.FS.default_fs.Dir('blat')
-
-        env = Environment(CPPPATH = [ 'foo', '$FOO/bar', blat ],
-                          INCPREFIX = 'foo ',
-                          INCSUFFIX = 'bar',
-                          FOO = 'baz',
-                          RDirs=RDirs)
+        assert flags == ['foobar', 'foobar', 'foobazbar'], flags
+
+        blat = env.fs.Dir('blat')
+
+        env.Replace(CPPPATH = [ 'foo', '$FOO/bar', blat ],
+                    INCPREFIX = 'foo ',
+                    INCSUFFIX = 'bar',
+                    FOO = 'baz')
         flags = env.subst_list('$_CPPINCFLAGS', 1)[0]
         flags = env.subst_list('$_CPPINCFLAGS', 1)[0]
-        assert len(flags) == 8, flags
-        assert flags[0] == '$(', \
-               flags[0]
-        assert flags[1] == os.path.normpath('foo'), \
-               flags[1]
-        assert flags[2] == os.path.normpath('xx/foobar'), \
-               flags[2]
-        assert flags[3] == os.path.normpath('foo'), \
-               flags[3]
-        assert flags[4] == os.path.normpath('xx/baz/bar'), \
-               flags[4]
-        assert flags[5] == os.path.normpath('foo'), \
-               flags[5]
-        assert flags[6] == os.path.normpath('blatbar'), \
-               flags[6]
-        assert flags[7] == '$)', \
-               flags[7]
-
-        env = Environment(F77PATH = [ 'foo', '$FOO/bar', blat ],
-                          INCPREFIX = 'foo ',
-                          INCSUFFIX = 'bar',
-                          FOO = 'baz',
-                          RDirs=RDirs)
+        expect = [ '$(',
+                   normalize_path('foo'),
+                   normalize_path('xx/foobar'),
+                   normalize_path('foo'),
+                   normalize_path('xx/baz/bar'),
+                   normalize_path('foo'),
+                   normalize_path('blatbar'),
+                   '$)',
+        ]
+        assert flags == expect, flags
+
+        env.Replace(F77PATH = [ 'foo', '$FOO/bar', blat ],
+                    INCPREFIX = 'foo ',
+                    INCSUFFIX = 'bar',
+                    FOO = 'baz')
         flags = env.subst_list('$_F77INCFLAGS', 1)[0]
         flags = env.subst_list('$_F77INCFLAGS', 1)[0]
-        assert len(flags) == 8, flags
-        assert flags[0] == '$(', \
-               flags[0]
-        assert flags[1] == os.path.normpath('foo'), \
-               flags[1]
-        assert flags[2] == os.path.normpath('xx/foobar'), \
-               flags[2]
-        assert flags[3] == os.path.normpath('foo'), \
-               flags[3]
-        assert flags[4] == os.path.normpath('xx/baz/bar'), \
-               flags[4]
-        assert flags[5] == os.path.normpath('foo'), \
-               flags[5]
-        assert flags[6] == os.path.normpath('blatbar'), \
-               flags[6]
-        assert flags[7] == '$)', \
-               flags[7]
-
-        env = Environment(CPPPATH = '', F77PATH = '', LIBPATH = '',
-                          RDirs=RDirs)
+        expect = [ '$(',
+                   normalize_path('foo'),
+                   normalize_path('xx/foobar'),
+                   normalize_path('foo'),
+                   normalize_path('xx/baz/bar'),
+                   normalize_path('foo'),
+                   normalize_path('blatbar'),
+                   '$)',
+        ]
+        assert flags == expect, flags
+
+        env.Replace(CPPPATH = '', F77PATH = '', LIBPATH = '')
         l = env.subst_list('$_CPPINCFLAGS')
         l = env.subst_list('$_CPPINCFLAGS')
-        assert len(l[0]) == 0, l[0]
+        assert l == [[]], l
         l = env.subst_list('$_F77INCFLAGS')
         l = env.subst_list('$_F77INCFLAGS')
-        assert len(l[0]) == 0, l[0]
+        assert l == [[]], l
         l = env.subst_list('$_LIBDIRFLAGS')
         l = env.subst_list('$_LIBDIRFLAGS')
-        assert len(l[0]) == 0, l[0]
-
-        SCons.Node.FS.default_fs.Repository('/rep1')
-        SCons.Node.FS.default_fs.Repository('/rep2')
-        env = Environment(CPPPATH = [ 'foo', '/a/b', '$FOO/bar', blat],
-                          INCPREFIX = '-I ',
-                          INCSUFFIX = 'XXX',
-                          FOO = 'baz',
-                          RDirs=RDirs)
+        assert l == [[]], l
+
+        env.fs.Repository('/rep1')
+        env.fs.Repository('/rep2')
+        env.Replace(CPPPATH = [ 'foo', '/a/b', '$FOO/bar', blat],
+                    INCPREFIX = '-I ',
+                    INCSUFFIX = 'XXX',
+                    FOO = 'baz')
         flags = env.subst_list('$_CPPINCFLAGS', 1)[0]
         flags = env.subst_list('$_CPPINCFLAGS', 1)[0]
-        assert flags[0] == '$(', \
-               flags[0]
-        assert flags[1] == '-I', \
-               flags[1]
-        assert flags[2] == os.path.normpath('xx/fooXXX'), \
-               flags[2]
-        assert flags[3] == '-I', \
-               flags[3]
-        assert flags[4] == os.path.normpath('/rep1/xx/fooXXX'), \
-               flags[4]
-        assert flags[5] == '-I', \
-               flags[5]
-        assert flags[6] == os.path.normpath('/rep2/xx/fooXXX'), \
-               flags[6]
-        assert flags[7] == '-I', \
-               flags[7]
-        assert flags[8] == os.path.normpath('/a/bXXX'), \
-               flags[8]
-        assert flags[9] == '-I', \
-               flags[9]
-        assert flags[10] == os.path.normpath('xx/baz/barXXX'), \
-               flags[10]
-        assert flags[11] == '-I', \
-               flags[11]
-        assert flags[12] == os.path.normpath('/rep1/xx/baz/barXXX'), \
-               flags[12]
-        assert flags[13] == '-I', \
-               flags[13]
-        assert flags[14] == os.path.normpath('/rep2/xx/baz/barXXX'), \
-               flags[14]
-        assert flags[15] == '-I', \
-               flags[15]
-        assert flags[16] == os.path.normpath('blatXXX'), \
-               flags[16]
-        assert flags[17] == '$)', \
-               flags[17]
+        expect = [ '$(',
+                   '-I', normalize_path('xx/fooXXX'),
+                   '-I', normalize_path('/rep1/xx/fooXXX'),
+                   '-I', normalize_path('/rep2/xx/fooXXX'),
+                   '-I', normalize_path('/a/bXXX'),
+                   '-I', normalize_path('xx/baz/barXXX'),
+                   '-I', normalize_path('/rep1/xx/baz/barXXX'),
+                   '-I', normalize_path('/rep2/xx/baz/barXXX'),
+                   '-I', normalize_path('blatXXX'),
+                   '$)'
+        ]
+        def normalize_if_path(arg, np=normalize_path):
+            if arg not in ('$(','$)','-I'):
+                return np(str(arg))
+            return arg
+        flags = list(map(normalize_if_path, flags))
+        assert flags == expect, flags
 
     def test_platform(self):
         """Test specifying a platform callable when instantiating."""
 
     def test_platform(self):
         """Test specifying a platform callable when instantiating."""
@@ -921,7 +1321,7 @@ class BaseTestCase(unittest.TestCase):
             env['SET_TOOL'] = 'initialized'
             assert env['PLATFORM'] == "TestPlatform"
 
             env['SET_TOOL'] = 'initialized'
             assert env['PLATFORM'] == "TestPlatform"
 
-        env = Environment(platform = platform(), tools = [tool])
+        env = self.TestEnvironment(platform = platform(), tools = [tool])
         assert env['XYZZY'] == 777, env
         assert env['PLATFORM'] == "TestPlatform"
         assert env['SET_TOOL'] == "initialized"
         assert env['XYZZY'] == 777, env
         assert env['PLATFORM'] == "TestPlatform"
         assert env['SET_TOOL'] == "initialized"
@@ -943,7 +1343,7 @@ class BaseTestCase(unittest.TestCase):
             SCons.Defaults.ConstructionEnvironment.update({
                 'PLATFORM' : platform(),
             })
             SCons.Defaults.ConstructionEnvironment.update({
                 'PLATFORM' : platform(),
             })
-            env = Environment(tools = [tool])
+            env = self.TestEnvironment(tools = [tool])
             assert env['XYZZY'] == 888, env
             assert env['PLATFORM'] == "DefaultTestPlatform"
             assert env['SET_TOOL'] == "abcde"
             assert env['XYZZY'] == 888, env
             assert env['PLATFORM'] == "DefaultTestPlatform"
             assert env['SET_TOOL'] == "abcde"
@@ -960,7 +1360,7 @@ class BaseTestCase(unittest.TestCase):
             env['AAA'] = env['XYZ']
         def t4(env):
             env['TOOL4'] = 444
             env['AAA'] = env['XYZ']
         def t4(env):
             env['TOOL4'] = 444
-        env = Environment(tools = [t1, t2, t3], XYZ = 'aaa')
+        env = self.TestEnvironment(tools = [t1, t2, t3], XYZ = 'aaa')
         assert env['TOOL1'] == 111, env['TOOL1']
         assert env['TOOL2'] == 222, env
         assert env['AAA'] == 'aaa', env
         assert env['TOOL1'] == 111, env['TOOL1']
         assert env['TOOL2'] == 222, env
         assert env['AAA'] == 'aaa', env
@@ -977,7 +1377,7 @@ def exists(env):
     return 1
 """)
 
     return 1
 """)
 
-        env = Environment(tools = [('faketool', {'a':1, 'b':2, 'c':3})],
+        env = self.TestEnvironment(tools = [('faketool', {'a':1, 'b':2, 'c':3})],
                           toolpath = [test.workpath('')])
         assert env['a'] == 1, env['a']
         assert env['b'] == 2, env['b']
                           toolpath = [test.workpath('')])
         assert env['a'] == 1, env['a']
         assert env['b'] == 2, env['b']
@@ -1015,20 +1415,20 @@ def exists(env):
             env['TOOL1'] = 111
         def t2(env):
             env['TOOL2'] = 222
             env['TOOL1'] = 111
         def t2(env):
             env['TOOL2'] = 222
-        env = Environment(tools = [t1, None, t2], XYZ = 'aaa')
+        env = self.TestEnvironment(tools = [t1, None, t2], XYZ = 'aaa')
         assert env['TOOL1'] == 111, env['TOOL1']
         assert env['TOOL2'] == 222, env
         assert env['XYZ'] == 'aaa', env
         assert env['TOOL1'] == 111, env['TOOL1']
         assert env['TOOL2'] == 222, env
         assert env['XYZ'] == 'aaa', env
-        env = Environment(tools = [None], XYZ = 'xyz')
+        env = self.TestEnvironment(tools = [None], XYZ = 'xyz')
         assert env['XYZ'] == 'xyz', env
         assert env['XYZ'] == 'xyz', env
-        env = Environment(tools = [t1, '', t2], XYZ = 'ddd')
+        env = self.TestEnvironment(tools = [t1, '', t2], XYZ = 'ddd')
         assert env['TOOL1'] == 111, env['TOOL1']
         assert env['TOOL2'] == 222, env
         assert env['XYZ'] == 'ddd', env
 
     def test_concat(self):
         "Test _concat()"
         assert env['TOOL1'] == 111, env['TOOL1']
         assert env['TOOL2'] == 222, env
         assert env['XYZ'] == 'ddd', env
 
     def test_concat(self):
         "Test _concat()"
-        e1 = Environment(PRE='pre', SUF='suf', STR='a b', LIST=['a', 'b'])
+        e1 = self.TestEnvironment(PRE='pre', SUF='suf', STR='a b', LIST=['a', 'b'])
         s = e1.subst
         x = s("${_concat('', '', '', __env__)}")
         assert x == '', x
         s = e1.subst
         x = s("${_concat('', '', '', __env__)}")
         assert x == '', x
@@ -1043,7 +1443,7 @@ def exists(env):
 
     def test_gvars(self):
         """Test the Environment gvars() method"""
 
     def test_gvars(self):
         """Test the Environment gvars() method"""
-        env = Environment(XXX = 'x', YYY = 'y', ZZZ = 'z')
+        env = self.TestEnvironment(XXX = 'x', YYY = 'y', ZZZ = 'z')
         gvars = env.gvars()
         assert gvars['XXX'] == 'x', gvars['XXX']
         assert gvars['YYY'] == 'y', gvars['YYY']
         gvars = env.gvars()
         assert gvars['XXX'] == 'x', gvars['XXX']
         assert gvars['YYY'] == 'y', gvars['YYY']
@@ -1051,7 +1451,7 @@ def exists(env):
 
     def test__update(self):
         """Test the _update() method"""
 
     def test__update(self):
         """Test the _update() method"""
-        env = Environment(X = 'x', Y = 'y', Z = 'z')
+        env = self.TestEnvironment(X = 'x', Y = 'y', Z = 'z')
         assert env['X'] == 'x', env['X']
         assert env['Y'] == 'y', env['Y']
         assert env['Z'] == 'z', env['Z']
         assert env['X'] == 'x', env['X']
         assert env['Y'] == 'y', env['Y']
         assert env['Z'] == 'z', env['Z']
@@ -1079,6 +1479,8 @@ def exists(env):
         b2 = Environment()['BUILDERS']
         assert b1 == b2, diff_dict(b1, b2)
 
         b2 = Environment()['BUILDERS']
         assert b1 == b2, diff_dict(b1, b2)
 
+        import UserDict
+        UD = UserDict.UserDict
         import UserList
         UL = UserList.UserList
 
         import UserList
         UL = UserList.UserList
 
@@ -1110,6 +1512,18 @@ def exists(env):
             UL(['i7']), [''],           UL(['i7', '']),
             UL(['i8']), UL(['']),       UL(['i8', '']),
 
             UL(['i7']), [''],           UL(['i7', '']),
             UL(['i8']), UL(['']),       UL(['i8', '']),
 
+            {'d1':1},   'D1',           {'d1':1, 'D1':None},
+            {'d2':1},   ['D2'],         {'d2':1, 'D2':None},
+            {'d3':1},   UL(['D3']),     {'d3':1, 'D3':None},
+            {'d4':1},   {'D4':1},       {'d4':1, 'D4':1},
+            {'d5':1},   UD({'D5':1}),   UD({'d5':1, 'D5':1}),
+
+            UD({'u1':1}), 'U1',         UD({'u1':1, 'U1':None}),
+            UD({'u2':1}), ['U2'],       UD({'u2':1, 'U2':None}),
+            UD({'u3':1}), UL(['U3']),   UD({'u3':1, 'U3':None}),
+            UD({'u4':1}), {'U4':1},     UD({'u4':1, 'U4':1}),
+            UD({'u5':1}), UD({'U5':1}), UD({'u5':1, 'U5':1}),
+
             '',         'M1',           'M1',
             '',         ['M2'],         ['M2'],
             '',         UL(['M3']),     UL(['M3']),
             '',         'M1',           'M1',
             '',         ['M2'],         ['M2'],
             '',         UL(['M3']),     UL(['M3']),
@@ -1160,14 +1574,21 @@ def exists(env):
         failed = 0
         while cases:
             input, append, expect = cases[:3]
         failed = 0
         while cases:
             input, append, expect = cases[:3]
-            env['XXX'] = input
-            env.Append(XXX = append)
-            result = env['XXX']
-            if result != expect:
+            env['XXX'] = copy.copy(input)
+            try:
+                env.Append(XXX = append)
+            except Exception, e:
                 if failed == 0: print
                 if failed == 0: print
-                print "    %s Append %s => %s did not match %s" % \
-                      (repr(input), repr(append), repr(result), repr(expect))
+                print "    %s Append %s exception: %s" % \
+                      (repr(input), repr(append), e)
                 failed = failed + 1
                 failed = failed + 1
+            else:
+                result = env['XXX']
+                if result != expect:
+                    if failed == 0: print
+                    print "    %s Append %s => %s did not match %s" % \
+                          (repr(input), repr(append), repr(result), repr(expect))
+                    failed = failed + 1
             del cases[:3]
         assert failed == 0, "%d Append() cases failed" % failed
 
             del cases[:3]
         assert failed == 0, "%d Append() cases failed" % failed
 
@@ -1193,80 +1614,140 @@ def exists(env):
 
         ccc = C('ccc')
 
 
         ccc = C('ccc')
 
-        env2 = Environment(CCC1 = ['c1'], CCC2 = ccc)
+        env2 = self.TestEnvironment(CCC1 = ['c1'], CCC2 = ccc)
         env2.Append(CCC1 = ccc, CCC2 = ['c2'])
         assert env2['CCC1'][0] == 'c1', env2['CCC1']
         assert env2['CCC1'][1] is ccc, env2['CCC1']
         assert env2['CCC2'][0] is ccc, env2['CCC2']
         assert env2['CCC2'][1] == 'c2', env2['CCC2']
 
         env2.Append(CCC1 = ccc, CCC2 = ['c2'])
         assert env2['CCC1'][0] == 'c1', env2['CCC1']
         assert env2['CCC1'][1] is ccc, env2['CCC1']
         assert env2['CCC2'][0] is ccc, env2['CCC2']
         assert env2['CCC2'][1] == 'c2', env2['CCC2']
 
-        env3 = Environment(X = {'x1' : 7})
+        env3 = self.TestEnvironment(X = {'x1' : 7})
         env3.Append(X = {'x1' : 8, 'x2' : 9}, Y = {'y1' : 10})
         assert env3['X'] == {'x1': 8, 'x2': 9}, env3['X']
         assert env3['Y'] == {'y1': 10}, env3['Y']
 
         env3.Append(X = {'x1' : 8, 'x2' : 9}, Y = {'y1' : 10})
         assert env3['X'] == {'x1': 8, 'x2': 9}, env3['X']
         assert env3['Y'] == {'y1': 10}, env3['Y']
 
-        env4 = Environment(BUILDERS = {'z1' : 11})
-        env4.Append(BUILDERS = {'z2' : 12})
-        assert env4['BUILDERS'] == {'z1' : 11, 'z2' : 12}, env4['BUILDERS']
+        z1 = Builder()
+        z2 = Builder()
+        env4 = self.TestEnvironment(BUILDERS = {'z1' : z1})
+        env4.Append(BUILDERS = {'z2' : z2})
+        assert env4['BUILDERS'] == {'z1' : z1, 'z2' : z2}, env4['BUILDERS']
         assert hasattr(env4, 'z1')
         assert hasattr(env4, 'z2')
 
     def test_AppendENVPath(self):
         """Test appending to an ENV path."""
         assert hasattr(env4, 'z1')
         assert hasattr(env4, 'z2')
 
     def test_AppendENVPath(self):
         """Test appending to an ENV path."""
-        env1 = Environment(ENV = {'PATH': r'C:\dir\num\one;C:\dir\num\two'},
+        env1 = self.TestEnvironment(ENV = {'PATH': r'C:\dir\num\one;C:\dir\num\two'},
                            MYENV = {'MYPATH': r'C:\mydir\num\one;C:\mydir\num\two'})
         # have to include the pathsep here so that the test will work on UNIX too.
         env1.AppendENVPath('PATH',r'C:\dir\num\two', sep = ';')
         env1.AppendENVPath('PATH',r'C:\dir\num\three', sep = ';')
         env1.AppendENVPath('MYPATH',r'C:\mydir\num\three','MYENV', sep = ';')
         env1.AppendENVPath('MYPATH',r'C:\mydir\num\one','MYENV', sep = ';')
                            MYENV = {'MYPATH': r'C:\mydir\num\one;C:\mydir\num\two'})
         # have to include the pathsep here so that the test will work on UNIX too.
         env1.AppendENVPath('PATH',r'C:\dir\num\two', sep = ';')
         env1.AppendENVPath('PATH',r'C:\dir\num\three', sep = ';')
         env1.AppendENVPath('MYPATH',r'C:\mydir\num\three','MYENV', sep = ';')
         env1.AppendENVPath('MYPATH',r'C:\mydir\num\one','MYENV', sep = ';')
+        # this should do nothing since delete_existing is 0
+        env1.AppendENVPath('MYPATH',r'C:\mydir\num\three','MYENV', sep = ';', delete_existing=0)
         assert(env1['ENV']['PATH'] == r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three')
         assert(env1['MYENV']['MYPATH'] == r'C:\mydir\num\two;C:\mydir\num\three;C:\mydir\num\one')
 
         assert(env1['ENV']['PATH'] == r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three')
         assert(env1['MYENV']['MYPATH'] == r'C:\mydir\num\two;C:\mydir\num\three;C:\mydir\num\one')
 
+        test = TestCmd.TestCmd(workdir = '')
+        test.subdir('sub1', 'sub2')
+        p=env1['ENV']['PATH']
+        env1.AppendENVPath('PATH','#sub1', sep = ';')
+        env1.AppendENVPath('PATH',env1.fs.Dir('sub2'), sep = ';')
+        assert env1['ENV']['PATH'] == p + ';sub1;sub2', env1['ENV']['PATH']
+
     def test_AppendUnique(self):
         """Test appending to unique values to construction variables
 
         This strips values that are already present when lists are
         involved."""
     def test_AppendUnique(self):
         """Test appending to unique values to construction variables
 
         This strips values that are already present when lists are
         involved."""
-        env = Environment(AAA1 = 'a1',
+        env = self.TestEnvironment(AAA1 = 'a1',
                           AAA2 = 'a2',
                           AAA3 = 'a3',
                           AAA2 = 'a2',
                           AAA3 = 'a3',
+                          AAA4 = 'a4',
+                          AAA5 = 'a5',
                           BBB1 = ['b1'],
                           BBB2 = ['b2'],
                           BBB3 = ['b3'],
                           BBB1 = ['b1'],
                           BBB2 = ['b2'],
                           BBB3 = ['b3'],
+                          BBB4 = ['b4'],
+                          BBB5 = ['b5'],
                           CCC1 = '',
                           CCC1 = '',
-                          CCC2 = '')
+                          CCC2 = '',
+                          DDD1 = ['a', 'b', 'c'])
         env.AppendUnique(AAA1 = 'a1',
                          AAA2 = ['a2'],
         env.AppendUnique(AAA1 = 'a1',
                          AAA2 = ['a2'],
-                         AAA3 = ['a3', 'b', 'c', 'a3'],
+                         AAA3 = ['a3', 'b', 'c', 'c', 'b', 'a3'], # ignore dups
+                         AAA4 = 'a4.new',
+                         AAA5 = ['a5.new'],
                          BBB1 = 'b1',
                          BBB2 = ['b2'],
                          BBB1 = 'b1',
                          BBB2 = ['b2'],
-                         BBB3 = ['b3', 'c', 'd', 'b3'],
+                         BBB3 = ['b3', 'c', 'd', 'c', 'b3'],
+                         BBB4 = 'b4.new',
+                         BBB5 = ['b5.new'],
                          CCC1 = 'c1',
                          CCC1 = 'c1',
-                         CCC2 = ['c2'])
+                         CCC2 = ['c2'],
+                         DDD1 = 'b')
 
         assert env['AAA1'] == 'a1a1', env['AAA1']
         assert env['AAA2'] == ['a2'], env['AAA2']
         assert env['AAA3'] == ['a3', 'b', 'c'], env['AAA3']
 
         assert env['AAA1'] == 'a1a1', env['AAA1']
         assert env['AAA2'] == ['a2'], env['AAA2']
         assert env['AAA3'] == ['a3', 'b', 'c'], env['AAA3']
+        assert env['AAA4'] == 'a4a4.new', env['AAA4']
+        assert env['AAA5'] == ['a5', 'a5.new'], env['AAA5']
         assert env['BBB1'] == ['b1'], env['BBB1']
         assert env['BBB2'] == ['b2'], env['BBB2']
         assert env['BBB3'] == ['b3', 'c', 'd'], env['BBB3']
         assert env['BBB1'] == ['b1'], env['BBB1']
         assert env['BBB2'] == ['b2'], env['BBB2']
         assert env['BBB3'] == ['b3', 'c', 'd'], env['BBB3']
+        assert env['BBB4'] == ['b4', 'b4.new'], env['BBB4']
+        assert env['BBB5'] == ['b5', 'b5.new'], env['BBB5']
         assert env['CCC1'] == 'c1', env['CCC1']
         assert env['CCC2'] == ['c2'], env['CCC2']
         assert env['CCC1'] == 'c1', env['CCC1']
         assert env['CCC2'] == ['c2'], env['CCC2']
+        assert env['DDD1'] == ['a', 'b', 'c'], env['DDD1']
+
+        env.AppendUnique(DDD1 = 'b', delete_existing=1)
+        assert env['DDD1'] == ['a', 'c', 'b'], env['DDD1'] # b moves to end
+        env.AppendUnique(DDD1 = ['a','b'], delete_existing=1)
+        assert env['DDD1'] == ['c', 'a', 'b'], env['DDD1'] # a & b move to end
+        env.AppendUnique(DDD1 = ['e','f', 'e'], delete_existing=1)
+        assert env['DDD1'] == ['c', 'a', 'b', 'f', 'e'], env['DDD1'] # add last
+        
+        env['CLVar'] = CLVar([])
+        env.AppendUnique(CLVar = 'bar')
+        result = env['CLVar']
+        if sys.version[0] == '1' or sys.version[:3] == '2.0':
+            # Python 2.0 and before have a quirky behavior where CLVar([])
+            # actually matches '' and [] due to different __coerce__()
+            # semantics in the UserList implementation.  It isn't worth a
+            # lot of effort to get this corner case to work identically
+            # (support for Python 1.5 support will die soon anyway),
+            # so just treat it separately for now.
+            assert result == 'bar', result
+        else:
+            assert isinstance(result, CLVar), repr(result)
+            assert result == ['bar'], result
 
 
-    def test_Copy(self):
-        """Test construction Environment copying
+        env['CLVar'] = CLVar(['abc'])
+        env.AppendUnique(CLVar = 'bar')
+        result = env['CLVar']
+        assert isinstance(result, CLVar), repr(result)
+        assert result == ['abc', 'bar'], result
+
+        env['CLVar'] = CLVar(['bar'])
+        env.AppendUnique(CLVar = 'bar')
+        result = env['CLVar']
+        assert isinstance(result, CLVar), repr(result)
+        assert result == ['bar'], result
+
+    def test_Clone(self):
+        """Test construction environment copying
 
         Update the copy independently afterwards and check that
         the original remains intact (that is, no dangling
         references point to objects in the copied environment).
 
         Update the copy independently afterwards and check that
         the original remains intact (that is, no dangling
         references point to objects in the copied environment).
-        Copy the original with some construction variable
+        Clone the original with some construction variable
         updates and check that the original remains intact
         and the copy has the updated values.
         """
         updates and check that the original remains intact
         and the copy has the updated values.
         """
-        env1 = Environment(XXX = 'x', YYY = 'y')
-        env2 = env1.Copy()
-        env1copy = env1.Copy()
+        env1 = self.TestEnvironment(XXX = 'x', YYY = 'y')
+        env2 = env1.Clone()
+        env1copy = env1.Clone()
         assert env1copy == env1copy
         assert env2 == env2
         env2.Replace(YYY = 'yyy')
         assert env1copy == env1copy
         assert env2 == env2
         env2.Replace(YYY = 'yyy')
@@ -1274,7 +1755,7 @@ def exists(env):
         assert env1 != env2
         assert env1 == env1copy
 
         assert env1 != env2
         assert env1 == env1copy
 
-        env3 = env1.Copy(XXX = 'x3', ZZZ = 'z3')
+        env3 = env1.Clone(XXX = 'x3', ZZZ = 'z3')
         assert env3 == env3
         assert env3.Dictionary('XXX') == 'x3'
         assert env3.Dictionary('YYY') == 'y'
         assert env3 == env3
         assert env3.Dictionary('XXX') == 'x3'
         assert env3.Dictionary('YYY') == 'y'
@@ -1285,38 +1766,38 @@ def exists(env):
         # deep copied, but not instances.
         class TestA:
             pass
         # deep copied, but not instances.
         class TestA:
             pass
-        env1 = Environment(XXX=TestA(), YYY = [ 1, 2, 3 ],
+        env1 = self.TestEnvironment(XXX=TestA(), YYY = [ 1, 2, 3 ],
                            ZZZ = { 1:2, 3:4 })
                            ZZZ = { 1:2, 3:4 })
-        env2=env1.Copy()
+        env2=env1.Clone()
         env2.Dictionary('YYY').append(4)
         env2.Dictionary('ZZZ')[5] = 6
         assert env1.Dictionary('XXX') is env2.Dictionary('XXX')
         assert 4 in env2.Dictionary('YYY')
         assert not 4 in env1.Dictionary('YYY')
         env2.Dictionary('YYY').append(4)
         env2.Dictionary('ZZZ')[5] = 6
         assert env1.Dictionary('XXX') is env2.Dictionary('XXX')
         assert 4 in env2.Dictionary('YYY')
         assert not 4 in env1.Dictionary('YYY')
-        assert env2.Dictionary('ZZZ').has_key(5)
-        assert not env1.Dictionary('ZZZ').has_key(5)
+        assert 5 in env2.Dictionary('ZZZ')
+        assert 5 not in env1.Dictionary('ZZZ')
 
         #
 
         #
-        env1 = Environment(BUILDERS = {'b1' : 1})
+        env1 = self.TestEnvironment(BUILDERS = {'b1' : Builder()})
         assert hasattr(env1, 'b1'), "env1.b1 was not set"
         assert hasattr(env1, 'b1'), "env1.b1 was not set"
-        assert env1.b1.env == env1, "b1.env doesn't point to env1"
-        env2 = env1.Copy(BUILDERS = {'b2' : 2})
+        assert env1.b1.object == env1, "b1.object doesn't point to env1"
+        env2 = env1.Clone(BUILDERS = {'b2' : Builder()})
         assert env2 is env2
         assert env2 == env2
         assert hasattr(env1, 'b1'), "b1 was mistakenly cleared from env1"
         assert env2 is env2
         assert env2 == env2
         assert hasattr(env1, 'b1'), "b1 was mistakenly cleared from env1"
-        assert env1.b1.env == env1, "b1.env was changed"
+        assert env1.b1.object == env1, "b1.object was changed"
         assert not hasattr(env2, 'b1'), "b1 was not cleared from env2"
         assert hasattr(env2, 'b2'), "env2.b2 was not set"
         assert not hasattr(env2, 'b1'), "b1 was not cleared from env2"
         assert hasattr(env2, 'b2'), "env2.b2 was not set"
-        assert env2.b2.env == env2, "b2.env doesn't point to env2"
+        assert env2.b2.object == env2, "b2.object doesn't point to env2"
 
         # Ensure that specifying new tools in a copied environment
         # works.
         def foo(env): env['FOO'] = 1
         def bar(env): env['BAR'] = 2
         def baz(env): env['BAZ'] = 3
 
         # Ensure that specifying new tools in a copied environment
         # works.
         def foo(env): env['FOO'] = 1
         def bar(env): env['BAR'] = 2
         def baz(env): env['BAZ'] = 3
-        env1 = Environment(tools=[foo])
-        env2 = env1.Copy()
-        env3 = env1.Copy(tools=[bar, baz])
+        env1 = self.TestEnvironment(tools=[foo])
+        env2 = env1.Clone()
+        env3 = env1.Clone(tools=[bar, baz])
 
         assert env1.get('FOO') is 1
         assert env1.get('BAR') is None
 
         assert env1.get('FOO') is 1
         assert env1.get('BAR') is None
@@ -1330,8 +1811,8 @@ def exists(env):
 
         # Ensure that recursive variable substitution when copying
         # environments works properly.
 
         # Ensure that recursive variable substitution when copying
         # environments works properly.
-        env1 = Environment(CCFLAGS = '-DFOO', XYZ = '-DXYZ')
-        env2 = env1.Copy(CCFLAGS = '$CCFLAGS -DBAR',
+        env1 = self.TestEnvironment(CCFLAGS = '-DFOO', XYZ = '-DXYZ')
+        env2 = env1.Clone(CCFLAGS = '$CCFLAGS -DBAR',
                          XYZ = ['-DABC', 'x $XYZ y', '-DDEF'])
         x = env2.get('CCFLAGS')
         assert x == '-DFOO -DBAR', x
                          XYZ = ['-DABC', 'x $XYZ y', '-DDEF'])
         x = env2.get('CCFLAGS')
         assert x == '-DFOO -DBAR', x
@@ -1340,14 +1821,68 @@ def exists(env):
 
         # Ensure that special properties of a class don't get
         # lost on copying.
 
         # Ensure that special properties of a class don't get
         # lost on copying.
-        env1 = Environment(FLAGS = CLVar('flag1 flag2'))
+        env1 = self.TestEnvironment(FLAGS = CLVar('flag1 flag2'))
         x = env1.get('FLAGS')
         assert x == ['flag1', 'flag2'], x
         x = env1.get('FLAGS')
         assert x == ['flag1', 'flag2'], x
-        env2 = env1.Copy()
+        env2 = env1.Clone()
         env2.Append(FLAGS = 'flag3 flag4')
         x = env2.get('FLAGS')
         assert x == ['flag1', 'flag2', 'flag3', 'flag4'], x
 
         env2.Append(FLAGS = 'flag3 flag4')
         x = env2.get('FLAGS')
         assert x == ['flag1', 'flag2', 'flag3', 'flag4'], x
 
+        # Test that the environment stores the toolpath and
+        # re-uses it for copies.
+        test = TestCmd.TestCmd(workdir = '')
+
+        test.write('xxx.py', """\
+def exists(env):
+    1
+def generate(env):
+    env['XXX'] = 'one'
+""")
+
+        test.write('yyy.py', """\
+def exists(env):
+    1
+def generate(env):
+    env['YYY'] = 'two'
+""")
+
+        env = self.TestEnvironment(tools=['xxx'], toolpath=[test.workpath('')])
+        assert env['XXX'] == 'one', env['XXX']
+        env = env.Clone(tools=['yyy'])
+        assert env['YYY'] == 'two', env['YYY']
+
+
+        # Test that
+        real_value = [4]
+
+        def my_tool(env, rv=real_value):
+            assert env['KEY_THAT_I_WANT'] == rv[0]
+            env['KEY_THAT_I_WANT'] = rv[0] + 1
+
+        env = self.TestEnvironment()
+
+        real_value[0] = 5
+        env = env.Clone(KEY_THAT_I_WANT=5, tools=[my_tool])
+        assert env['KEY_THAT_I_WANT'] == real_value[0], env['KEY_THAT_I_WANT']
+
+        real_value[0] = 6
+        env = env.Clone(KEY_THAT_I_WANT=6, tools=[my_tool])
+        assert env['KEY_THAT_I_WANT'] == real_value[0], env['KEY_THAT_I_WANT']
+
+
+    def test_Copy(self):
+        """Test copying using the old env.Copy() method"""
+        env1 = self.TestEnvironment(XXX = 'x', YYY = 'y')
+        env2 = env1.Copy()
+        env1copy = env1.Copy()
+        assert env1copy == env1copy
+        assert env2 == env2
+        env2.Replace(YYY = 'yyy')
+        assert env2 == env2
+        assert env1 != env2
+        assert env1 == env1copy
+
     def test_Detect(self):
         """Test Detect()ing tools"""
         test = TestCmd.TestCmd(workdir = '')
     def test_Detect(self):
         """Test Detect()ing tools"""
         test = TestCmd.TestCmd(workdir = '')
@@ -1359,14 +1894,14 @@ def exists(env):
             test.write(['sub1', 'xxx'], "sub1/xxx\n")
             test.write(['sub2', 'xxx'], "sub2/xxx\n")
 
             test.write(['sub1', 'xxx'], "sub1/xxx\n")
             test.write(['sub2', 'xxx'], "sub2/xxx\n")
 
-            env = Environment(ENV = { 'PATH' : [sub1, sub2] })
+            env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] })
 
             x = env.Detect('xxx.exe')
             assert x is None, x
 
             test.write(['sub2', 'xxx.exe'], "sub2/xxx.exe\n")
 
 
             x = env.Detect('xxx.exe')
             assert x is None, x
 
             test.write(['sub2', 'xxx.exe'], "sub2/xxx.exe\n")
 
-            env = Environment(ENV = { 'PATH' : [sub1, sub2] })
+            env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] })
 
             x = env.Detect('xxx.exe')
             assert x == 'xxx.exe', x
 
             x = env.Detect('xxx.exe')
             assert x == 'xxx.exe', x
@@ -1380,7 +1915,7 @@ def exists(env):
             test.write(['sub1', 'xxx.exe'], "sub1/xxx.exe\n")
             test.write(['sub2', 'xxx.exe'], "sub2/xxx.exe\n")
 
             test.write(['sub1', 'xxx.exe'], "sub1/xxx.exe\n")
             test.write(['sub2', 'xxx.exe'], "sub2/xxx.exe\n")
 
-            env = Environment(ENV = { 'PATH' : [sub1, sub2] })
+            env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] })
 
             x = env.Detect('xxx.exe')
             assert x is None, x
 
             x = env.Detect('xxx.exe')
             assert x is None, x
@@ -1388,7 +1923,7 @@ def exists(env):
             sub2_xxx_exe = test.workpath('sub2', 'xxx.exe')
             os.chmod(sub2_xxx_exe, 0755)
 
             sub2_xxx_exe = test.workpath('sub2', 'xxx.exe')
             os.chmod(sub2_xxx_exe, 0755)
 
-            env = Environment(ENV = { 'PATH' : [sub1, sub2] })
+            env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] })
 
             x = env.Detect('xxx.exe')
             assert x == 'xxx.exe', x
 
             x = env.Detect('xxx.exe')
             assert x == 'xxx.exe', x
@@ -1399,37 +1934,37 @@ def exists(env):
             x = env.Detect('xxx.exe')
             assert x == 'xxx.exe', x
 
             x = env.Detect('xxx.exe')
             assert x == 'xxx.exe', x
 
-        env = Environment(ENV = { 'PATH' : [] })
+        env = self.TestEnvironment(ENV = { 'PATH' : [] })
         x = env.Detect('xxx.exe')
         assert x is None, x
 
     def test_Dictionary(self):
         x = env.Detect('xxx.exe')
         assert x is None, x
 
     def test_Dictionary(self):
-       """Test retrieval of known construction variables
-
-       Fetch them from the Dictionary and check for well-known
-       defaults that get inserted.
-       """
-       env = Environment(XXX = 'x', YYY = 'y', ZZZ = 'z')
-       assert env.Dictionary('XXX') == 'x'
-       assert env.Dictionary('YYY') == 'y'
-       assert env.Dictionary('XXX', 'ZZZ') == ['x', 'z']
-       xxx, zzz = env.Dictionary('XXX', 'ZZZ')
-       assert xxx == 'x'
-       assert zzz == 'z'
-       assert env.Dictionary().has_key('BUILDERS')
-       assert env.Dictionary().has_key('CC')
-       assert env.Dictionary().has_key('CCFLAGS')
-       assert env.Dictionary().has_key('ENV')
-
-       assert env['XXX'] == 'x'
-       env['XXX'] = 'foo'
-       assert env.Dictionary('XXX') == 'foo'
-       del env['XXX']
-       assert not env.Dictionary().has_key('XXX')
+        """Test retrieval of known construction variables
+
+        Fetch them from the Dictionary and check for well-known
+        defaults that get inserted.
+        """
+        env = self.TestEnvironment(XXX = 'x', YYY = 'y', ZZZ = 'z')
+        assert env.Dictionary('XXX') == 'x'
+        assert env.Dictionary('YYY') == 'y'
+        assert env.Dictionary('XXX', 'ZZZ') == ['x', 'z']
+        xxx, zzz = env.Dictionary('XXX', 'ZZZ')
+        assert xxx == 'x'
+        assert zzz == 'z'
+        assert 'BUILDERS' in env.Dictionary()
+        assert 'CC' in env.Dictionary()
+        assert 'CCFLAGS' in env.Dictionary()
+        assert 'ENV' in env.Dictionary()
+
+        assert env['XXX'] == 'x'
+        env['XXX'] = 'foo'
+        assert env.Dictionary('XXX') == 'foo'
+        del env['XXX']
+        assert 'XXX' not in env.Dictionary()
 
     def test_FindIxes(self):
         "Test FindIxes()"
 
     def test_FindIxes(self):
         "Test FindIxes()"
-        env = Environment(LIBPREFIX='lib',
+        env = self.TestEnvironment(LIBPREFIX='lib',
                           LIBSUFFIX='.a',
                           SHLIBPREFIX='lib',
                           SHLIBSUFFIX='.so',
                           LIBSUFFIX='.a',
                           SHLIBPREFIX='lib',
                           SHLIBSUFFIX='.so',
@@ -1441,62 +1976,84 @@ def exists(env):
 
         assert paths[0] == env.FindIxes(paths, 'LIBPREFIX', 'LIBSUFFIX')
         assert paths[1] == env.FindIxes(paths, 'SHLIBPREFIX', 'SHLIBSUFFIX')
 
         assert paths[0] == env.FindIxes(paths, 'LIBPREFIX', 'LIBSUFFIX')
         assert paths[1] == env.FindIxes(paths, 'SHLIBPREFIX', 'SHLIBSUFFIX')
-        assert None == env.FindIxes(paths, 'PREFIX', 'POST')
+        assert None is env.FindIxes(paths, 'PREFIX', 'POST')
 
         paths = ['libfoo.a', 'prefoopost']
 
         assert paths[0] == env.FindIxes(paths, 'LIBPREFIX', 'LIBSUFFIX')
 
         paths = ['libfoo.a', 'prefoopost']
 
         assert paths[0] == env.FindIxes(paths, 'LIBPREFIX', 'LIBSUFFIX')
-        assert None == env.FindIxes(paths, 'SHLIBPREFIX', 'SHLIBSUFFIX')
+        assert None is env.FindIxes(paths, 'SHLIBPREFIX', 'SHLIBSUFFIX')
         assert paths[1] == env.FindIxes(paths, 'PREFIX', 'SUFFIX')
 
     def test_ParseConfig(self):
         """Test the ParseConfig() method"""
         assert paths[1] == env.FindIxes(paths, 'PREFIX', 'SUFFIX')
 
     def test_ParseConfig(self):
         """Test the ParseConfig() method"""
-        env = Environment(ASFLAGS='assembler',
-                          COMMAND='command',
+        env = self.TestEnvironment(COMMAND='command',
+                          ASFLAGS='assembler',
+                          CCFLAGS=[''],
+                          CPPDEFINES=[],
                           CPPFLAGS=[''],
                           CPPPATH='string',
                           CPPFLAGS=[''],
                           CPPPATH='string',
+                          FRAMEWORKPATH=[],
+                          FRAMEWORKS=[],
                           LIBPATH=['list'],
                           LIBS='',
                           LINKFLAGS=[''],
                           LIBPATH=['list'],
                           LIBS='',
                           LINKFLAGS=[''],
-                          CCFLAGS=[''])
-        orig_popen = os.popen
-        class my_popen:
+                          RPATH=[])
+
+        orig_backtick = env.backtick
+        class my_backtick:
             def __init__(self, save_command, output):
                 self.save_command = save_command
                 self.output = output
             def __call__(self, command):
                 self.save_command.append(command)
             def __init__(self, save_command, output):
                 self.save_command = save_command
                 self.output = output
             def __call__(self, command):
                 self.save_command.append(command)
-                class fake_file:
-                    def __init__(self, output):
-                        self.output = output
-                    def read(self):
-                        return self.output
-                return fake_file(self.output)
+                return self.output
+
         try:
             save_command = []
         try:
             save_command = []
-            os.popen = my_popen(save_command, 
+            env.backtick = my_backtick(save_command, 
                                  "-I/usr/include/fum -I bar -X\n" + \
                                  "-L/usr/fax -L foo -lxxx -l yyy " + \
                                  "-I/usr/include/fum -I bar -X\n" + \
                                  "-L/usr/fax -L foo -lxxx -l yyy " + \
-                                 "-Wa,-as -Wl,-link -Wp,-cpp abc " + \
-                                 "-pthread -framework Carbon " + \
-                                 "-mno-cygwin -mwindows")
+                                 "-Wa,-as -Wl,-link " + \
+                                 "-Wl,-rpath=rpath1 " + \
+                                 "-Wl,-R,rpath2 " + \
+                                 "-Wl,-Rrpath3 " + \
+                                 "-Wp,-cpp abc " + \
+                                 "-framework Carbon " + \
+                                 "-frameworkdir=fwd1 " + \
+                                 "-Ffwd2 " + \
+                                 "-F fwd3 " + \
+                                 "-pthread " + \
+                                 "-mno-cygwin -mwindows " + \
+                                 "-arch i386 -isysroot /tmp +DD64 " + \
+                                 "-DFOO -DBAR=value")
             env.ParseConfig("fake $COMMAND")
             assert save_command == ['fake command'], save_command
             env.ParseConfig("fake $COMMAND")
             assert save_command == ['fake command'], save_command
-            assert env['ASFLAGS'] == ['assembler', '-Wa,-as'], env['ASFLAGS']
-            assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar'], env['CPPPATH']
+            assert env['ASFLAGS'] == ['assembler', '-as'], env['ASFLAGS']
+            assert env['CCFLAGS'] == ['', '-X', '-Wa,-as',
+                                      '-pthread', '-mno-cygwin',
+                                      ('-arch', 'i386'), ('-isysroot', '/tmp'),
+                                      '+DD64'], env['CCFLAGS']
+            assert env['CPPDEFINES'] == ['FOO', ['BAR', 'value']], env['CPPDEFINES']
             assert env['CPPFLAGS'] == ['', '-Wp,-cpp'], env['CPPFLAGS']
             assert env['CPPFLAGS'] == ['', '-Wp,-cpp'], env['CPPFLAGS']
+            assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar'], env['CPPPATH']
+            assert env['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], env['FRAMEWORKPATH']
+            assert env['FRAMEWORKS'] == ['Carbon'], env['FRAMEWORKS']
             assert env['LIBPATH'] == ['list', '/usr/fax', 'foo'], env['LIBPATH']
             assert env['LIBS'] == ['xxx', 'yyy', env.File('abc')], env['LIBS']
             assert env['LIBPATH'] == ['list', '/usr/fax', 'foo'], env['LIBPATH']
             assert env['LIBS'] == ['xxx', 'yyy', env.File('abc')], env['LIBS']
-            assert env['LINKFLAGS'] == ['', '-Wl,-link', '-pthread', '-framework', 'Carbon', '-mno-cygwin', '-mwindows'], env['LINKFLAGS']
-            assert env['CCFLAGS'] == ['', '-X', '-pthread', '-mno-cygwin'], env['CCFLAGS']
-
-            os.popen = my_popen([], "-Ibar")
+            assert env['LINKFLAGS'] == ['', '-Wl,-link', '-pthread',
+                                        '-mno-cygwin', '-mwindows',
+                                        ('-arch', 'i386'),
+                                        ('-isysroot', '/tmp'),
+                                        '+DD64'], env['LINKFLAGS']
+            assert env['RPATH'] == ['rpath1', 'rpath2', 'rpath3'], env['RPATH']
+
+            env.backtick = my_backtick([], "-Ibar")
             env.ParseConfig("fake2")
             assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar'], env['CPPPATH']
             env.ParseConfig("fake2", unique=0)
             assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar', 'bar'], env['CPPPATH']
         finally:
             env.ParseConfig("fake2")
             assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar'], env['CPPPATH']
             env.ParseConfig("fake2", unique=0)
             assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar', 'bar'], env['CPPPATH']
         finally:
-            os.popen = orig_popen
+            env.backtick = orig_backtick
 
     def test_ParseDepends(self):
         """Test the ParseDepends() method"""
 
     def test_ParseDepends(self):
         """Test the ParseDepends() method"""
@@ -1523,7 +2080,7 @@ f5: \
    mno \
 """)
 
    mno \
 """)
 
-        env = Environment(SINGLE = test.workpath('single'))
+        env = self.TestEnvironment(SINGLE = test.workpath('single'))
 
         tlist = []
         dlist = []
 
         tlist = []
         dlist = []
@@ -1546,8 +2103,8 @@ f5: \
         del dlist[:]
 
         env.ParseDepends('$SINGLE', only_one=1)
         del dlist[:]
 
         env.ParseDepends('$SINGLE', only_one=1)
-        t = map(str, tlist)
-        d = map(str, dlist)
+        t = list(map(str, tlist))
+        d = list(map(str, dlist))
         assert t == ['f0'], t
         assert d == ['d1', 'd2', 'd3'], d
 
         assert t == ['f0'], t
         assert d == ['d1', 'd2', 'd3'], d
 
@@ -1555,8 +2112,8 @@ f5: \
         del dlist[:]
 
         env.ParseDepends(test.workpath('multiple'))
         del dlist[:]
 
         env.ParseDepends(test.workpath('multiple'))
-        t = map(str, tlist)
-        d = map(str, dlist)
+        t = list(map(str, tlist))
+        d = list(map(str, dlist))
         assert t == ['f1', 'f2', 'f3', 'f4', 'f5'], t
         assert d == ['foo', 'bar', 'abc', 'def', 'ghi', 'jkl', 'mno'], d
 
         assert t == ['f1', 'f2', 'f3', 'f4', 'f5'], t
         assert d == ['foo', 'bar', 'abc', 'def', 'ghi', 'jkl', 'mno'], d
 
@@ -1569,7 +2126,7 @@ f5: \
 
     def test_Platform(self):
         """Test the Platform() method"""
 
     def test_Platform(self):
         """Test the Platform() method"""
-        env = Environment(WIN32='win32', NONE='no-such-platform')
+        env = self.TestEnvironment(WIN32='win32', NONE='no-such-platform')
 
         exc_caught = None
         try:
 
         exc_caught = None
         try:
@@ -1594,6 +2151,8 @@ f5: \
     def test_Prepend(self):
         """Test prepending to construction variables in an Environment
         """
     def test_Prepend(self):
         """Test prepending to construction variables in an Environment
         """
+        import UserDict
+        UD = UserDict.UserDict
         import UserList
         UL = UserList.UserList
 
         import UserList
         UL = UserList.UserList
 
@@ -1625,6 +2184,18 @@ f5: \
             UL(['i7']), [''],           UL(['', 'i7']),
             UL(['i8']), UL(['']),       UL(['', 'i8']),
 
             UL(['i7']), [''],           UL(['', 'i7']),
             UL(['i8']), UL(['']),       UL(['', 'i8']),
 
+            {'d1':1},   'D1',           {'d1':1, 'D1':None},
+            {'d2':1},   ['D2'],         {'d2':1, 'D2':None},
+            {'d3':1},   UL(['D3']),     {'d3':1, 'D3':None},
+            {'d4':1},   {'D4':1},       {'d4':1, 'D4':1},
+            {'d5':1},   UD({'D5':1}),   UD({'d5':1, 'D5':1}),
+
+            UD({'u1':1}), 'U1',         UD({'u1':1, 'U1':None}),
+            UD({'u2':1}), ['U2'],       UD({'u2':1, 'U2':None}),
+            UD({'u3':1}), UL(['U3']),   UD({'u3':1, 'U3':None}),
+            UD({'u4':1}), {'U4':1},     UD({'u4':1, 'U4':1}),
+            UD({'u5':1}), UD({'U5':1}), UD({'u5':1, 'U5':1}),
+
             '',         'M1',           'M1',
             '',         ['M2'],         ['M2'],
             '',         UL(['M3']),     UL(['M3']),
             '',         'M1',           'M1',
             '',         ['M2'],         ['M2'],
             '',         UL(['M3']),     UL(['M3']),
@@ -1675,14 +2246,21 @@ f5: \
         failed = 0
         while cases:
             input, prepend, expect = cases[:3]
         failed = 0
         while cases:
             input, prepend, expect = cases[:3]
-            env['XXX'] = input
-            env.Prepend(XXX = prepend)
-            result = env['XXX']
-            if result != expect:
+            env['XXX'] = copy.copy(input)
+            try:
+                env.Prepend(XXX = prepend)
+            except Exception, e:
                 if failed == 0: print
                 if failed == 0: print
-                print "    %s Prepend %s => %s did not match %s" % \
-                      (repr(input), repr(prepend), repr(result), repr(expect))
+                print "    %s Prepend %s exception: %s" % \
+                      (repr(input), repr(prepend), e)
                 failed = failed + 1
                 failed = failed + 1
+            else:
+                result = env['XXX']
+                if result != expect:
+                    if failed == 0: print
+                    print "    %s Prepend %s => %s did not match %s" % \
+                          (repr(input), repr(prepend), repr(result), repr(expect))
+                    failed = failed + 1
             del cases[:3]
         assert failed == 0, "%d Prepend() cases failed" % failed
 
             del cases[:3]
         assert failed == 0, "%d Prepend() cases failed" % failed
 
@@ -1698,91 +2276,142 @@ f5: \
         assert isinstance(result, CLVar), repr(result)
         assert result == ['bar', 'foo'], result
 
         assert isinstance(result, CLVar), repr(result)
         assert result == ['bar', 'foo'], result
 
-        env3 = Environment(X = {'x1' : 7})
+        env3 = self.TestEnvironment(X = {'x1' : 7})
         env3.Prepend(X = {'x1' : 8, 'x2' : 9}, Y = {'y1' : 10})
         assert env3['X'] == {'x1': 8, 'x2' : 9}, env3['X']
         assert env3['Y'] == {'y1': 10}, env3['Y']
 
         env3.Prepend(X = {'x1' : 8, 'x2' : 9}, Y = {'y1' : 10})
         assert env3['X'] == {'x1': 8, 'x2' : 9}, env3['X']
         assert env3['Y'] == {'y1': 10}, env3['Y']
 
-        env4 = Environment(BUILDERS = {'z1' : 11})
-        env4.Prepend(BUILDERS = {'z2' : 12})
-        assert env4['BUILDERS'] == {'z1' : 11, 'z2' : 12}, env4['BUILDERS']
+        z1 = Builder()
+        z2 = Builder()
+        env4 = self.TestEnvironment(BUILDERS = {'z1' : z1})
+        env4.Prepend(BUILDERS = {'z2' : z2})
+        assert env4['BUILDERS'] == {'z1' : z1, 'z2' : z2}, env4['BUILDERS']
         assert hasattr(env4, 'z1')
         assert hasattr(env4, 'z2')
 
     def test_PrependENVPath(self):
         """Test prepending to an ENV path."""
         assert hasattr(env4, 'z1')
         assert hasattr(env4, 'z2')
 
     def test_PrependENVPath(self):
         """Test prepending to an ENV path."""
-        env1 = Environment(ENV = {'PATH': r'C:\dir\num\one;C:\dir\num\two'},
+        env1 = self.TestEnvironment(ENV = {'PATH': r'C:\dir\num\one;C:\dir\num\two'},
                            MYENV = {'MYPATH': r'C:\mydir\num\one;C:\mydir\num\two'})
         # have to include the pathsep here so that the test will work on UNIX too.
         env1.PrependENVPath('PATH',r'C:\dir\num\two',sep = ';')
         env1.PrependENVPath('PATH',r'C:\dir\num\three',sep = ';')
         env1.PrependENVPath('MYPATH',r'C:\mydir\num\three','MYENV',sep = ';')
         env1.PrependENVPath('MYPATH',r'C:\mydir\num\one','MYENV',sep = ';')
                            MYENV = {'MYPATH': r'C:\mydir\num\one;C:\mydir\num\two'})
         # have to include the pathsep here so that the test will work on UNIX too.
         env1.PrependENVPath('PATH',r'C:\dir\num\two',sep = ';')
         env1.PrependENVPath('PATH',r'C:\dir\num\three',sep = ';')
         env1.PrependENVPath('MYPATH',r'C:\mydir\num\three','MYENV',sep = ';')
         env1.PrependENVPath('MYPATH',r'C:\mydir\num\one','MYENV',sep = ';')
+        # this should do nothing since delete_existing is 0
+        env1.PrependENVPath('MYPATH',r'C:\mydir\num\three','MYENV', sep = ';', delete_existing=0)
         assert(env1['ENV']['PATH'] == r'C:\dir\num\three;C:\dir\num\two;C:\dir\num\one')
         assert(env1['MYENV']['MYPATH'] == r'C:\mydir\num\one;C:\mydir\num\three;C:\mydir\num\two')
 
         assert(env1['ENV']['PATH'] == r'C:\dir\num\three;C:\dir\num\two;C:\dir\num\one')
         assert(env1['MYENV']['MYPATH'] == r'C:\mydir\num\one;C:\mydir\num\three;C:\mydir\num\two')
 
-    def test_PrependENVPath(self):
-        """Test prepending to an ENV path."""
-        env1 = Environment(ENV = {'PATH': r'C:\dir\num\one;C:\dir\num\two'},
-                           MYENV = {'MYPATH': r'C:\mydir\num\one;C:\mydir\num\two'})
-        # have to include the pathsep here so that the test will work on UNIX too.
-        env1.PrependENVPath('PATH',r'C:\dir\num\two',sep = ';')
-        env1.PrependENVPath('PATH',r'C:\dir\num\three',sep = ';')
-        env1.PrependENVPath('MYPATH',r'C:\mydir\num\three','MYENV',sep = ';')
-        env1.PrependENVPath('MYPATH',r'C:\mydir\num\one','MYENV',sep = ';')
-        assert(env1['ENV']['PATH'] == r'C:\dir\num\three;C:\dir\num\two;C:\dir\num\one')
-        assert(env1['MYENV']['MYPATH'] == r'C:\mydir\num\one;C:\mydir\num\three;C:\mydir\num\two')
+        test = TestCmd.TestCmd(workdir = '')
+        test.subdir('sub1', 'sub2')
+        p=env1['ENV']['PATH']
+        env1.PrependENVPath('PATH','#sub1', sep = ';')
+        env1.PrependENVPath('PATH',env1.fs.Dir('sub2'), sep = ';')
+        assert env1['ENV']['PATH'] == 'sub2;sub1;' + p, env1['ENV']['PATH']
 
     def test_PrependUnique(self):
         """Test prepending unique values to construction variables
 
         This strips values that are already present when lists are
         involved."""
 
     def test_PrependUnique(self):
         """Test prepending unique values to construction variables
 
         This strips values that are already present when lists are
         involved."""
-        env = Environment(AAA1 = 'a1',
+        env = self.TestEnvironment(AAA1 = 'a1',
                           AAA2 = 'a2',
                           AAA3 = 'a3',
                           AAA2 = 'a2',
                           AAA3 = 'a3',
+                          AAA4 = 'a4',
+                          AAA5 = 'a5',
                           BBB1 = ['b1'],
                           BBB2 = ['b2'],
                           BBB3 = ['b3'],
                           BBB1 = ['b1'],
                           BBB2 = ['b2'],
                           BBB3 = ['b3'],
+                          BBB4 = ['b4'],
+                          BBB5 = ['b5'],
                           CCC1 = '',
                           CCC1 = '',
-                          CCC2 = '')
+                          CCC2 = '',
+                          DDD1 = ['a', 'b', 'c'])
         env.PrependUnique(AAA1 = 'a1',
                           AAA2 = ['a2'],
         env.PrependUnique(AAA1 = 'a1',
                           AAA2 = ['a2'],
-                          AAA3 = ['a3', 'b', 'c', 'a3'],
+                          AAA3 = ['a3', 'b', 'c', 'b', 'a3'], # ignore dups
+                          AAA4 = 'a4.new',
+                          AAA5 = ['a5.new'],
                           BBB1 = 'b1',
                           BBB2 = ['b2'],
                           BBB3 = ['b3', 'b', 'c', 'b3'],
                           BBB1 = 'b1',
                           BBB2 = ['b2'],
                           BBB3 = ['b3', 'b', 'c', 'b3'],
+                          BBB4 = 'b4.new',
+                          BBB5 = ['b5.new'],
                           CCC1 = 'c1',
                           CCC1 = 'c1',
-                          CCC2 = ['c2'])
+                          CCC2 = ['c2'],
+                          DDD1 = 'b')
         assert env['AAA1'] == 'a1a1', env['AAA1']
         assert env['AAA2'] == ['a2'], env['AAA2']
         assert env['AAA1'] == 'a1a1', env['AAA1']
         assert env['AAA2'] == ['a2'], env['AAA2']
-        assert env['AAA3'] == ['b', 'c', 'a3'], env['AAA3']
+        assert env['AAA3'] == ['c', 'b', 'a3'], env['AAA3']
+        assert env['AAA4'] == 'a4.newa4', env['AAA4']
+        assert env['AAA5'] == ['a5.new', 'a5'], env['AAA5']
         assert env['BBB1'] == ['b1'], env['BBB1']
         assert env['BBB2'] == ['b2'], env['BBB2']
         assert env['BBB3'] == ['b', 'c', 'b3'], env['BBB3']
         assert env['BBB1'] == ['b1'], env['BBB1']
         assert env['BBB2'] == ['b2'], env['BBB2']
         assert env['BBB3'] == ['b', 'c', 'b3'], env['BBB3']
+        assert env['BBB4'] == ['b4.new', 'b4'], env['BBB4']
+        assert env['BBB5'] == ['b5.new', 'b5'], env['BBB5']
         assert env['CCC1'] == 'c1', env['CCC1']
         assert env['CCC2'] == ['c2'], env['CCC2']
         assert env['CCC1'] == 'c1', env['CCC1']
         assert env['CCC2'] == ['c2'], env['CCC2']
+        assert env['DDD1'] == ['a', 'b', 'c'], env['DDD1']
+
+        env.PrependUnique(DDD1 = 'b', delete_existing=1)
+        assert env['DDD1'] == ['b', 'a', 'c'], env['DDD1'] # b moves to front
+        env.PrependUnique(DDD1 = ['a','c'], delete_existing=1)
+        assert env['DDD1'] == ['a', 'c', 'b'], env['DDD1'] # a & c move to front
+        env.PrependUnique(DDD1 = ['d','e','d'], delete_existing=1)
+        assert env['DDD1'] == ['d', 'e', 'a', 'c', 'b'], env['DDD1'] 
+
+
+        env['CLVar'] = CLVar([])
+        env.PrependUnique(CLVar = 'bar')
+        result = env['CLVar']
+        if sys.version[0] == '1' or sys.version[:3] == '2.0':
+            # Python 2.0 and before have a quirky behavior where CLVar([])
+            # actually matches '' and [] due to different __coerce__()
+            # semantics in the UserList implementation.  It isn't worth a
+            # lot of effort to get this corner case to work identically
+            # (support for Python 1.5 support will die soon anyway),
+            # so just treat it separately for now.
+            assert result == 'bar', result
+        else:
+            assert isinstance(result, CLVar), repr(result)
+            assert result == ['bar'], result
+
+        env['CLVar'] = CLVar(['abc'])
+        env.PrependUnique(CLVar = 'bar')
+        result = env['CLVar']
+        assert isinstance(result, CLVar), repr(result)
+        assert result == ['bar', 'abc'], result
+
+        env['CLVar'] = CLVar(['bar'])
+        env.PrependUnique(CLVar = 'bar')
+        result = env['CLVar']
+        assert isinstance(result, CLVar), repr(result)
+        assert result == ['bar'], result
 
     def test_Replace(self):
         """Test replacing construction variables in an Environment
 
         After creation of the Environment, of course.
         """
 
     def test_Replace(self):
         """Test replacing construction variables in an Environment
 
         After creation of the Environment, of course.
         """
-        env1 = Environment(AAA = 'a', BBB = 'b')
+        env1 = self.TestEnvironment(AAA = 'a', BBB = 'b')
         env1.Replace(BBB = 'bbb', CCC = 'ccc')
 
         env1.Replace(BBB = 'bbb', CCC = 'ccc')
 
-        env2 = Environment(AAA = 'a', BBB = 'bbb', CCC = 'ccc')
+        env2 = self.TestEnvironment(AAA = 'a', BBB = 'bbb', CCC = 'ccc')
         assert env1 == env2, diff_env(env1, env2)
 
         assert env1 == env2, diff_env(env1, env2)
 
-        env3 = Environment(BUILDERS = {'b1' : 1})
+        b1 = Builder()
+        b2 = Builder()
+        env3 = self.TestEnvironment(BUILDERS = {'b1' : b1})
         assert hasattr(env3, 'b1'), "b1 was not set"
         assert hasattr(env3, 'b1'), "b1 was not set"
-        env3.Replace(BUILDERS = {'b2' : 2})
+        env3.Replace(BUILDERS = {'b2' : b2})
         assert not hasattr(env3, 'b1'), "b1 was not cleared"
         assert hasattr(env3, 'b2'), "b2 was not set"
 
     def test_ReplaceIxes(self):
         "Test ReplaceIxes()"
         assert not hasattr(env3, 'b1'), "b1 was not cleared"
         assert hasattr(env3, 'b2'), "b2 was not set"
 
     def test_ReplaceIxes(self):
         "Test ReplaceIxes()"
-        env = Environment(LIBPREFIX='lib',
+        env = self.TestEnvironment(LIBPREFIX='lib',
                           LIBSUFFIX='.a',
                           SHLIBPREFIX='lib',
                           SHLIBSUFFIX='.so',
                           LIBSUFFIX='.a',
                           SHLIBPREFIX='lib',
                           SHLIBSUFFIX='.so',
@@ -1803,7 +2432,7 @@ f5: \
 
     def test_SetDefault(self):
         """Test the SetDefault method"""
 
     def test_SetDefault(self):
         """Test the SetDefault method"""
-        env = Environment(tools = [])
+        env = self.TestEnvironment(tools = [])
         env.SetDefault(V1 = 1)
         env.SetDefault(V1 = 2)
         assert env['V1'] == 1
         env.SetDefault(V1 = 1)
         env.SetDefault(V1 = 2)
         assert env['V1'] == 1
@@ -1813,21 +2442,21 @@ f5: \
 
     def test_Tool(self):
         """Test the Tool() method"""
 
     def test_Tool(self):
         """Test the Tool() method"""
-        env = Environment(LINK='link', NONE='no-such-tool')
+        env = self.TestEnvironment(LINK='link', NONE='no-such-tool')
 
         exc_caught = None
         try:
             env.Tool('does_not_exist')
 
         exc_caught = None
         try:
             env.Tool('does_not_exist')
-        except SCons.Errors.UserError:
+        except SCons.Errors.EnvironmentError:
             exc_caught = 1
             exc_caught = 1
-        assert exc_caught, "did not catch expected UserError"
+        assert exc_caught, "did not catch expected EnvironmentError"
 
         exc_caught = None
         try:
             env.Tool('$NONE')
 
         exc_caught = None
         try:
             env.Tool('$NONE')
-        except SCons.Errors.UserError:
+        except SCons.Errors.EnvironmentError:
             exc_caught = 1
             exc_caught = 1
-        assert exc_caught, "did not catch expected UserError"
+        assert exc_caught, "did not catch expected EnvironmentError"
 
         # Use a non-existent toolpath directory just to make sure we
         # can call Tool() with the keyword argument.
 
         # Use a non-existent toolpath directory just to make sure we
         # can call Tool() with the keyword argument.
@@ -1837,6 +2466,29 @@ f5: \
         env.Tool('$LINK')
         assert env['LINK'] == '$SMARTLINK', env['LINK']
 
         env.Tool('$LINK')
         assert env['LINK'] == '$SMARTLINK', env['LINK']
 
+        # Test that the environment stores the toolpath and
+        # re-uses it for later calls.
+        test = TestCmd.TestCmd(workdir = '')
+
+        test.write('xxx.py', """\
+def exists(env):
+    1
+def generate(env):
+    env['XXX'] = 'one'
+""")
+
+        test.write('yyy.py', """\
+def exists(env):
+    1
+def generate(env):
+    env['YYY'] = 'two'
+""")
+
+        env = self.TestEnvironment(tools=['xxx'], toolpath=[test.workpath('')])
+        assert env['XXX'] == 'one', env['XXX']
+        env.Tool('yyy')
+        assert env['YYY'] == 'two', env['YYY']
+
     def test_WhereIs(self):
         """Test the WhereIs() method"""
         test = TestCmd.TestCmd(workdir = '')
     def test_WhereIs(self):
         """Test the WhereIs() method"""
         test = TestCmd.TestCmd(workdir = '')
@@ -1865,21 +2517,21 @@ f5: \
                           test.workpath('sub2'),
                           test.workpath('sub3'),
                           test.workpath('sub4'),
                           test.workpath('sub2'),
                           test.workpath('sub3'),
                           test.workpath('sub4'),
-                        ] + string.split(env_path, os.pathsep)
+                        ] + env_path.split(os.pathsep)
 
         pathdirs_1243 = [ test.workpath('sub1'),
                           test.workpath('sub2'),
                           test.workpath('sub4'),
                           test.workpath('sub3'),
 
         pathdirs_1243 = [ test.workpath('sub1'),
                           test.workpath('sub2'),
                           test.workpath('sub4'),
                           test.workpath('sub3'),
-                        ] + string.split(env_path, os.pathsep)
+                        ] + env_path.split(os.pathsep)
 
 
-        path = string.join(pathdirs_1234, os.pathsep)
-        env = Environment(ENV = {'PATH' : path})
+        path = os.pathsep.join(pathdirs_1234)
+        env = self.TestEnvironment(ENV = {'PATH' : path})
         wi = env.WhereIs('xxx.exe')
         assert wi == test.workpath(sub3_xxx_exe), wi
         wi = env.WhereIs('xxx.exe', pathdirs_1243)
         assert wi == test.workpath(sub4_xxx_exe), wi
         wi = env.WhereIs('xxx.exe')
         assert wi == test.workpath(sub3_xxx_exe), wi
         wi = env.WhereIs('xxx.exe', pathdirs_1243)
         assert wi == test.workpath(sub4_xxx_exe), wi
-        wi = env.WhereIs('xxx.exe', string.join(pathdirs_1243, os.pathsep))
+        wi = env.WhereIs('xxx.exe', os.pathsep.join(pathdirs_1243))
         assert wi == test.workpath(sub4_xxx_exe), wi
 
         wi = env.WhereIs('xxx.exe', reject = sub3_xxx_exe)
         assert wi == test.workpath(sub4_xxx_exe), wi
 
         wi = env.WhereIs('xxx.exe', reject = sub3_xxx_exe)
@@ -1887,13 +2539,13 @@ f5: \
         wi = env.WhereIs('xxx.exe', pathdirs_1243, reject = sub3_xxx_exe)
         assert wi == test.workpath(sub4_xxx_exe), wi
 
         wi = env.WhereIs('xxx.exe', pathdirs_1243, reject = sub3_xxx_exe)
         assert wi == test.workpath(sub4_xxx_exe), wi
 
-        path = string.join(pathdirs_1243, os.pathsep)
-        env = Environment(ENV = {'PATH' : path})
+        path = os.pathsep.join(pathdirs_1243)
+        env = self.TestEnvironment(ENV = {'PATH' : path})
         wi = env.WhereIs('xxx.exe')
         assert wi == test.workpath(sub4_xxx_exe), wi
         wi = env.WhereIs('xxx.exe', pathdirs_1234)
         assert wi == test.workpath(sub3_xxx_exe), wi
         wi = env.WhereIs('xxx.exe')
         assert wi == test.workpath(sub4_xxx_exe), wi
         wi = env.WhereIs('xxx.exe', pathdirs_1234)
         assert wi == test.workpath(sub3_xxx_exe), wi
-        wi = env.WhereIs('xxx.exe', string.join(pathdirs_1234, os.pathsep))
+        wi = env.WhereIs('xxx.exe', os.pathsep.join(pathdirs_1234))
         assert wi == test.workpath(sub3_xxx_exe), wi
 
         if sys.platform == 'win32':
         assert wi == test.workpath(sub3_xxx_exe), wi
 
         if sys.platform == 'win32':
@@ -1904,13 +2556,13 @@ f5: \
             assert wi == test.workpath(sub4_xxx_exe), wi
 
             wi = env.WhereIs('xxx', path = pathdirs_1234, pathext = '.BAT;.EXE')
             assert wi == test.workpath(sub4_xxx_exe), wi
 
             wi = env.WhereIs('xxx', path = pathdirs_1234, pathext = '.BAT;.EXE')
-            assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi
+            assert wi.lower() == test.workpath(sub3_xxx_exe).lower(), wi
 
             # Test that we return a normalized path even when
             # the path contains forward slashes.
             forward_slash = test.workpath('') + '/sub3'
             wi = env.WhereIs('xxx', path = forward_slash, pathext = '.EXE')
 
             # Test that we return a normalized path even when
             # the path contains forward slashes.
             forward_slash = test.workpath('') + '/sub3'
             wi = env.WhereIs('xxx', path = forward_slash, pathext = '.EXE')
-            assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi
+            assert wi.lower() == test.workpath(sub3_xxx_exe).lower(), wi
 
 
 
 
 
 
@@ -1918,29 +2570,33 @@ f5: \
         """Test the Action() method"""
         import SCons.Action
 
         """Test the Action() method"""
         import SCons.Action
 
-        env = Environment(FOO = 'xyzzy')
+        env = self.TestEnvironment(FOO = 'xyzzy')
 
         a = env.Action('foo')
         assert a, a
 
         a = env.Action('foo')
         assert a, a
-        assert a.__class__ is SCons.Action.CommandAction, a
+        assert a.__class__ is SCons.Action.CommandAction, a.__class__
 
         a = env.Action('$FOO')
         assert a, a
 
         a = env.Action('$FOO')
         assert a, a
-        assert a.__class__ is SCons.Action.LazyAction, a
+        assert a.__class__ is SCons.Action.CommandAction, a.__class__
+
+        a = env.Action('$$FOO')
+        assert a, a
+        assert a.__class__ is SCons.Action.LazyAction, a.__class__
 
         a = env.Action(['$FOO', 'foo'])
         assert a, a
 
         a = env.Action(['$FOO', 'foo'])
         assert a, a
-        assert a.__class__ is SCons.Action.ListAction, a
+        assert a.__class__ is SCons.Action.ListAction, a.__class__
 
         def func(arg):
             pass
         a = env.Action(func)
         assert a, a
 
         def func(arg):
             pass
         a = env.Action(func)
         assert a, a
-        assert a.__class__ is SCons.Action.FunctionAction, a
+        assert a.__class__ is SCons.Action.FunctionAction, a.__class__
 
     def test_AddPostAction(self):
         """Test the AddPostAction() method"""
 
     def test_AddPostAction(self):
         """Test the AddPostAction() method"""
-        env = Environment(FOO='fff', BAR='bbb')
+        env = self.TestEnvironment(FOO='fff', BAR='bbb')
 
         n = env.AddPostAction('$FOO', lambda x: x)
         assert str(n[0]) == 'fff', n[0]
 
         n = env.AddPostAction('$FOO', lambda x: x)
         assert str(n[0]) == 'fff', n[0]
@@ -1951,7 +2607,7 @@ f5: \
 
     def test_AddPreAction(self):
         """Test the AddPreAction() method"""
 
     def test_AddPreAction(self):
         """Test the AddPreAction() method"""
-        env = Environment(FOO='fff', BAR='bbb')
+        env = self.TestEnvironment(FOO='fff', BAR='bbb')
 
         n = env.AddPreAction('$FOO', lambda x: x)
         assert str(n[0]) == 'fff', n[0]
 
         n = env.AddPreAction('$FOO', lambda x: x)
         assert str(n[0]) == 'fff', n[0]
@@ -1962,7 +2618,7 @@ f5: \
 
     def test_Alias(self):
         """Test the Alias() method"""
 
     def test_Alias(self):
         """Test the Alias() method"""
-        env = Environment(FOO='kkk', BAR='lll', EA='export_alias')
+        env = self.TestEnvironment(FOO='kkk', BAR='lll', EA='export_alias')
 
         tgt = env.Alias('new_alias')[0]
         assert str(tgt) == 'new_alias', tgt
 
         tgt = env.Alias('new_alias')[0]
         assert str(tgt) == 'new_alias', tgt
@@ -1979,37 +2635,37 @@ f5: \
 
         tgt = env.Alias('export_alias', [ 'asrc1', '$FOO' ])[0]
         assert str(tgt) == 'export_alias', tgt
 
         tgt = env.Alias('export_alias', [ 'asrc1', '$FOO' ])[0]
         assert str(tgt) == 'export_alias', tgt
-        assert len(tgt.sources) == 2, map(str, tgt.sources)
-        assert str(tgt.sources[0]) == 'asrc1', map(str, tgt.sources)
-        assert str(tgt.sources[1]) == 'kkk', map(str, tgt.sources)
+        assert len(tgt.sources) == 2, list(map(str, tgt.sources))
+        assert str(tgt.sources[0]) == 'asrc1', list(map(str, tgt.sources))
+        assert str(tgt.sources[1]) == 'kkk', list(map(str, tgt.sources))
 
         n = env.Alias(tgt, source = ['$BAR', 'asrc4'])[0]
         assert n is tgt, n
 
         n = env.Alias(tgt, source = ['$BAR', 'asrc4'])[0]
         assert n is tgt, n
-        assert len(tgt.sources) == 4, map(str, tgt.sources)
-        assert str(tgt.sources[2]) == 'lll', map(str, tgt.sources)
-        assert str(tgt.sources[3]) == 'asrc4', map(str, tgt.sources)
+        assert len(tgt.sources) == 4, list(map(str, tgt.sources))
+        assert str(tgt.sources[2]) == 'lll', list(map(str, tgt.sources))
+        assert str(tgt.sources[3]) == 'asrc4', list(map(str, tgt.sources))
 
         n = env.Alias('$EA', 'asrc5')[0]
         assert n is tgt, n
 
         n = env.Alias('$EA', 'asrc5')[0]
         assert n is tgt, n
-        assert len(tgt.sources) == 5, map(str, tgt.sources)
-        assert str(tgt.sources[4]) == 'asrc5', map(str, tgt.sources)
+        assert len(tgt.sources) == 5, list(map(str, tgt.sources))
+        assert str(tgt.sources[4]) == 'asrc5', list(map(str, tgt.sources))
 
         t1, t2 = env.Alias(['t1', 't2'], ['asrc6', 'asrc7'])
         assert str(t1) == 't1', t1
         assert str(t2) == 't2', t2
 
         t1, t2 = env.Alias(['t1', 't2'], ['asrc6', 'asrc7'])
         assert str(t1) == 't1', t1
         assert str(t2) == 't2', t2
-        assert len(t1.sources) == 2, map(str, t1.sources)
-        assert str(t1.sources[0]) == 'asrc6', map(str, t1.sources)
-        assert str(t1.sources[1]) == 'asrc7', map(str, t1.sources)
-        assert len(t2.sources) == 2, map(str, t2.sources)
-        assert str(t2.sources[0]) == 'asrc6', map(str, t2.sources)
-        assert str(t2.sources[1]) == 'asrc7', map(str, t2.sources)
+        assert len(t1.sources) == 2, list(map(str, t1.sources))
+        assert str(t1.sources[0]) == 'asrc6', list(map(str, t1.sources))
+        assert str(t1.sources[1]) == 'asrc7', list(map(str, t1.sources))
+        assert len(t2.sources) == 2, list(map(str, t2.sources))
+        assert str(t2.sources[0]) == 'asrc6', list(map(str, t2.sources))
+        assert str(t2.sources[1]) == 'asrc7', list(map(str, t2.sources))
 
         tgt = env.Alias('add', 's1')
         tgt = env.Alias('add', 's2')[0]
 
         tgt = env.Alias('add', 's1')
         tgt = env.Alias('add', 's2')[0]
-        s = map(str, tgt.sources)
+        s = list(map(str, tgt.sources))
         assert s == ['s1', 's2'], s
         tgt = env.Alias(tgt, 's3')[0]
         assert s == ['s1', 's2'], s
         tgt = env.Alias(tgt, 's3')[0]
-        s = map(str, tgt.sources)
+        s = list(map(str, tgt.sources))
         assert s == ['s1', 's2', 's3'], s
 
         tgt = env.Alias('act', None, "action1")[0]
         assert s == ['s1', 's2', 's3'], s
 
         tgt = env.Alias('act', None, "action1")[0]
@@ -2024,85 +2680,87 @@ f5: \
 
     def test_AlwaysBuild(self):
         """Test the AlwaysBuild() method"""
 
     def test_AlwaysBuild(self):
         """Test the AlwaysBuild() method"""
-        env = Environment(FOO='fff', BAR='bbb')
-        t = env.AlwaysBuild('a', 'b$FOO', ['c', 'd'], '$BAR')
-        assert t[0].__class__.__name__ == 'File'
+        env = self.TestEnvironment(FOO='fff', BAR='bbb')
+        t = env.AlwaysBuild('a', 'b$FOO', ['c', 'd'], '$BAR',
+                            env.fs.Dir('dir'), env.fs.File('file'))
+        assert t[0].__class__.__name__ == 'Entry'
         assert t[0].path == 'a'
         assert t[0].always_build
         assert t[0].path == 'a'
         assert t[0].always_build
-        assert t[1].__class__.__name__ == 'File'
+        assert t[1].__class__.__name__ == 'Entry'
         assert t[1].path == 'bfff'
         assert t[1].always_build
         assert t[1].path == 'bfff'
         assert t[1].always_build
-        assert t[2].__class__.__name__ == 'File'
+        assert t[2].__class__.__name__ == 'Entry'
         assert t[2].path == 'c'
         assert t[2].always_build
         assert t[2].path == 'c'
         assert t[2].always_build
-        assert t[3].__class__.__name__ == 'File'
+        assert t[3].__class__.__name__ == 'Entry'
         assert t[3].path == 'd'
         assert t[3].always_build
         assert t[3].path == 'd'
         assert t[3].always_build
-        assert t[4].__class__.__name__ == 'File'
+        assert t[4].__class__.__name__ == 'Entry'
         assert t[4].path == 'bbb'
         assert t[4].always_build
         assert t[4].path == 'bbb'
         assert t[4].always_build
-
-    def test_BuildDir(self):
-        """Test the BuildDir() method"""
+        assert t[5].__class__.__name__ == 'Dir'
+        assert t[5].path == 'dir'
+        assert t[5].always_build
+        assert t[6].__class__.__name__ == 'File'
+        assert t[6].path == 'file'
+        assert t[6].always_build
+
+    def test_VariantDir(self):
+        """Test the VariantDir() method"""
         class MyFS:
              def Dir(self, name):
                  return name
         class MyFS:
              def Dir(self, name):
                  return name
-             def BuildDir(self, build_dir, src_dir, duplicate):
-                 self.build_dir = build_dir
+             def VariantDir(self, variant_dir, src_dir, duplicate):
+                 self.variant_dir = variant_dir
                  self.src_dir = src_dir
                  self.duplicate = duplicate
 
                  self.src_dir = src_dir
                  self.duplicate = duplicate
 
-        env = Environment(FOO = 'fff', BAR = 'bbb')
+        env = self.TestEnvironment(FOO = 'fff', BAR = 'bbb')
         env.fs = MyFS()
 
         env.fs = MyFS()
 
-        env.BuildDir('build', 'src')
-        assert env.fs.build_dir == 'build', env.fs.build_dir
+        env.VariantDir('build', 'src')
+        assert env.fs.variant_dir == 'build', env.fs.variant_dir
         assert env.fs.src_dir == 'src', env.fs.src_dir
         assert env.fs.duplicate == 1, env.fs.duplicate
 
         assert env.fs.src_dir == 'src', env.fs.src_dir
         assert env.fs.duplicate == 1, env.fs.duplicate
 
-        env.BuildDir('build${FOO}', '${BAR}src', 0)
-        assert env.fs.build_dir == 'buildfff', env.fs.build_dir
+        env.VariantDir('build${FOO}', '${BAR}src', 0)
+        assert env.fs.variant_dir == 'buildfff', env.fs.variant_dir
         assert env.fs.src_dir == 'bbbsrc', env.fs.src_dir
         assert env.fs.duplicate == 0, env.fs.duplicate
 
     def test_Builder(self):
         """Test the Builder() method"""
         assert env.fs.src_dir == 'bbbsrc', env.fs.src_dir
         assert env.fs.duplicate == 0, env.fs.duplicate
 
     def test_Builder(self):
         """Test the Builder() method"""
-        env = Environment(FOO = 'xyzzy')
+        env = self.TestEnvironment(FOO = 'xyzzy')
 
         b = env.Builder(action = 'foo')
 
         b = env.Builder(action = 'foo')
-        assert not b is None, b
+        assert b is not None, b
 
         b = env.Builder(action = '$FOO')
 
         b = env.Builder(action = '$FOO')
-        assert not b is None, b
+        assert b is not None, b
 
         b = env.Builder(action = ['$FOO', 'foo'])
 
         b = env.Builder(action = ['$FOO', 'foo'])
-        assert not b is None, b
+        assert b is not None, b
 
         def func(arg):
             pass
         b = env.Builder(action = func)
 
         def func(arg):
             pass
         b = env.Builder(action = func)
-        assert not b is None, b
+        assert b is not None, b
         b = env.Builder(generator = func)
         b = env.Builder(generator = func)
-        assert not b is None, b
+        assert b is not None, b
 
     def test_CacheDir(self):
         """Test the CacheDir() method"""
 
     def test_CacheDir(self):
         """Test the CacheDir() method"""
-        class MyFS:
-            def CacheDir(self, path):
-                self.CD = path
-
-        env = Environment(CD = 'CacheDir')
-        env.fs = MyFS()
+        env = self.TestEnvironment(CD = 'CacheDir')
 
         env.CacheDir('foo')
 
         env.CacheDir('foo')
-        assert env.fs.CD == 'foo', env.fs.CD
+        assert env._CacheDir_path == 'foo', env._CacheDir_path
 
         env.CacheDir('$CD')
 
         env.CacheDir('$CD')
-        assert env.fs.CD == 'CacheDir', env.fs.CD
+        assert env._CacheDir_path == 'CacheDir', env._CacheDir_path
 
     def test_Clean(self):
         """Test the Clean() method"""
 
     def test_Clean(self):
         """Test the Clean() method"""
-        env = Environment(FOO = 'fff', BAR = 'bbb')
+        env = self.TestEnvironment(FOO = 'fff', BAR = 'bbb')
 
         CT = SCons.Environment.CleanTargets
 
 
         CT = SCons.Environment.CleanTargets
 
@@ -2110,20 +2768,20 @@ f5: \
         fff = env.arg2nodes('fff')[0]
 
         t = env.Clean('foo', 'aaa')
         fff = env.arg2nodes('fff')[0]
 
         t = env.Clean('foo', 'aaa')
-        l = map(str, CT[foo])
+        l = list(map(str, CT[foo]))
         assert l == ['aaa'], l
 
         t = env.Clean(foo, ['$BAR', 'ccc'])
         assert l == ['aaa'], l
 
         t = env.Clean(foo, ['$BAR', 'ccc'])
-        l = map(str, CT[foo])
+        l = list(map(str, CT[foo]))
         assert l == ['aaa', 'bbb', 'ccc'], l
 
         eee = env.arg2nodes('eee')[0]
 
         t = env.Clean('$FOO', 'ddd')
         assert l == ['aaa', 'bbb', 'ccc'], l
 
         eee = env.arg2nodes('eee')[0]
 
         t = env.Clean('$FOO', 'ddd')
-        l = map(str, CT[fff])
+        l = list(map(str, CT[fff]))
         assert l == ['ddd'], l
         t = env.Clean(fff, [eee, 'fff'])
         assert l == ['ddd'], l
         t = env.Clean(fff, [eee, 'fff'])
-        l = map(str, CT[fff])
+        l = list(map(str, CT[fff]))
         assert l == ['ddd', 'eee', 'fff'], l
 
     def test_Command(self):
         assert l == ['ddd', 'eee', 'fff'], l
 
     def test_Command(self):
@@ -2131,37 +2789,37 @@ f5: \
         env = Environment()
         t = env.Command(target='foo.out', source=['foo1.in', 'foo2.in'],
                         action='buildfoo $target $source')[0]
         env = Environment()
         t = env.Command(target='foo.out', source=['foo1.in', 'foo2.in'],
                         action='buildfoo $target $source')[0]
-        assert not t.builder is None
+        assert t.builder is not None
         assert t.builder.action.__class__.__name__ == 'CommandAction'
         assert t.builder.action.cmd_list == 'buildfoo $target $source'
         assert t.builder.action.__class__.__name__ == 'CommandAction'
         assert t.builder.action.cmd_list == 'buildfoo $target $source'
-        assert 'foo1.in' in map(lambda x: x.path, t.sources)
-        assert 'foo2.in' in map(lambda x: x.path, t.sources)
+        assert 'foo1.in' in [x.path for x in t.sources]
+        assert 'foo2.in' in [x.path for x in t.sources]
 
 
-        sub = SCons.Node.FS.default_fs.Dir('sub')
+        sub = env.fs.Dir('sub')
         t = env.Command(target='bar.out', source='sub',
                         action='buildbar $target $source')[0]
         t = env.Command(target='bar.out', source='sub',
                         action='buildbar $target $source')[0]
-        assert 'sub' in map(lambda x: x.path, t.sources)
+        assert 'sub' in [x.path for x in t.sources]
 
         def testFunc(env, target, source):
             assert str(target[0]) == 'foo.out'
 
         def testFunc(env, target, source):
             assert str(target[0]) == 'foo.out'
-            assert 'foo1.in' in map(str, source) and 'foo2.in' in map(str, source), map(str, source)
+            assert 'foo1.in' in list(map(str, source)) and 'foo2.in' in list(map(str, source)), list(map(str, source))
             return 0
         t = env.Command(target='foo.out', source=['foo1.in','foo2.in'],
                         action=testFunc)[0]
             return 0
         t = env.Command(target='foo.out', source=['foo1.in','foo2.in'],
                         action=testFunc)[0]
-        assert not t.builder is None
+        assert t.builder is not None
         assert t.builder.action.__class__.__name__ == 'FunctionAction'
         t.build()
         assert t.builder.action.__class__.__name__ == 'FunctionAction'
         t.build()
-        assert 'foo1.in' in map(lambda x: x.path, t.sources)
-        assert 'foo2.in' in map(lambda x: x.path, t.sources)
+        assert 'foo1.in' in [x.path for x in t.sources]
+        assert 'foo2.in' in [x.path for x in t.sources]
 
         x = []
         def test2(baz, x=x):
             x.append(baz)
 
         x = []
         def test2(baz, x=x):
             x.append(baz)
-        env = Environment(TEST2 = test2)
+        env = self.TestEnvironment(TEST2 = test2)
         t = env.Command(target='baz.out', source='baz.in',
                         action='${TEST2(XYZ)}',
                         XYZ='magic word')[0]
         t = env.Command(target='baz.out', source='baz.in',
                         action='${TEST2(XYZ)}',
                         XYZ='magic word')[0]
-        assert not t.builder is None
+        assert t.builder is not None
         t.build()
         assert x[0] == 'magic word', x
 
         t.build()
         assert x[0] == 'magic word', x
 
@@ -2169,9 +2827,9 @@ f5: \
                         action = 'foo',
                         X = 'xxx')[0]
         assert str(t) == 'xxx.out', str(t)
                         action = 'foo',
                         X = 'xxx')[0]
         assert str(t) == 'xxx.out', str(t)
-        assert 'xxx.in' in map(lambda x: x.path, t.sources)
+        assert 'xxx.in' in [x.path for x in t.sources]
 
 
-        env = Environment(source_scanner = 'should_not_find_this')
+        env = self.TestEnvironment(source_scanner = 'should_not_find_this')
         t = env.Command(target='file.out', source='file.in',
                         action = 'foo',
                         source_scanner = 'fake')[0]
         t = env.Command(target='file.out', source='file.in',
                         action = 'foo',
                         source_scanner = 'fake')[0]
@@ -2186,17 +2844,17 @@ f5: \
         try:
             os.chdir(test.workpath())
 
         try:
             os.chdir(test.workpath())
 
-            env = Environment(FOO = 'xyzzy')
+            env = self.TestEnvironment(FOO = 'xyzzy')
 
             def func(arg):
                 pass
 
             c = env.Configure()
 
             def func(arg):
                 pass
 
             c = env.Configure()
-            assert not c is None, c
+            assert c is not None, c
             c.Finish()
 
             c = env.Configure(custom_tests = {'foo' : func, '$FOO' : func})
             c.Finish()
 
             c = env.Configure(custom_tests = {'foo' : func, '$FOO' : func})
-            assert not c is None, c
+            assert c is not None, c
             assert hasattr(c, 'foo')
             assert hasattr(c, 'xyzzy')
             c.Finish()
             assert hasattr(c, 'foo')
             assert hasattr(c, 'xyzzy')
             c.Finish()
@@ -2205,7 +2863,7 @@ f5: \
 
     def test_Depends(self):
         """Test the explicit Depends method."""
 
     def test_Depends(self):
         """Test the explicit Depends method."""
-        env = Environment(FOO = 'xxx', BAR='yyy')
+        env = self.TestEnvironment(FOO = 'xxx', BAR='yyy')
         env.Dir('dir1')
         env.Dir('dir2')
         env.File('xxx.py')
         env.Dir('dir1')
         env.Dir('dir2')
         env.File('xxx.py')
@@ -2241,7 +2899,7 @@ f5: \
             def Dir(self, name):
                 return 'Dir(%s)' % name
 
             def Dir(self, name):
                 return 'Dir(%s)' % name
 
-        env = Environment(FOO = 'foodir', BAR = 'bardir')
+        env = self.TestEnvironment(FOO = 'foodir', BAR = 'bardir')
         env.fs = MyFS()
 
         d = env.Dir('d')
         env.fs = MyFS()
 
         d = env.Dir('d')
@@ -2253,16 +2911,45 @@ f5: \
         d = env.Dir('${BAR}_$BAR')
         assert d == 'Dir(bardir_bardir)', d
 
         d = env.Dir('${BAR}_$BAR')
         assert d == 'Dir(bardir_bardir)', d
 
+        d = env.Dir(['dir1'])
+        assert d == ['Dir(dir1)'], d
+
+        d = env.Dir(['dir1', 'dir2'])
+        assert d == ['Dir(dir1)', 'Dir(dir2)'], d
+
+    def test_NoClean(self):
+        """Test the NoClean() method"""
+        env = self.TestEnvironment(FOO='ggg', BAR='hhh')
+        env.Dir('p_hhhb')
+        env.File('p_d')
+        t = env.NoClean('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO')
+
+        assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__
+        assert t[0].path == 'p_a'
+        assert t[0].noclean
+        assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__
+        assert t[1].path == 'p_hhhb'
+        assert t[1].noclean
+        assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__
+        assert t[2].path == 'p_c'
+        assert t[2].noclean
+        assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__
+        assert t[3].path == 'p_d'
+        assert t[3].noclean
+        assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__
+        assert t[4].path == 'p_ggg'
+        assert t[4].noclean
+
     def test_Dump(self):
         """Test the Dump() method"""
 
     def test_Dump(self):
         """Test the Dump() method"""
 
-        env = Environment(FOO = 'foo')
+        env = self.TestEnvironment(FOO = 'foo')
         assert env.Dump('FOO') == "'foo'", env.Dump('FOO')
         assert len(env.Dump()) > 200, env.Dump()    # no args version
 
     def test_Environment(self):
         """Test the Environment() method"""
         assert env.Dump('FOO') == "'foo'", env.Dump('FOO')
         assert len(env.Dump()) > 200, env.Dump()    # no args version
 
     def test_Environment(self):
         """Test the Environment() method"""
-        env = Environment(FOO = 'xxx', BAR = 'yyy')
+        env = self.TestEnvironment(FOO = 'xxx', BAR = 'yyy')
 
         e2 = env.Environment(X = '$FOO', Y = '$BAR')
         assert e2['X'] == 'xxx', e2['X']
 
         e2 = env.Environment(X = '$FOO', Y = '$BAR')
         assert e2['X'] == 'xxx', e2['X']
@@ -2283,13 +2970,37 @@ f5: \
         result = env.Execute("foo")
         assert result == "foo executed", result
 
         result = env.Execute("foo")
         assert result == "foo executed", result
 
+    def test_Entry(self):
+        """Test the Entry() method"""
+        class MyFS:
+            def Entry(self, name):
+                return 'Entry(%s)' % name
+
+        env = self.TestEnvironment(FOO = 'fooentry', BAR = 'barentry')
+        env.fs = MyFS()
+
+        e = env.Entry('e')
+        assert e == 'Entry(e)', e
+
+        e = env.Entry('$FOO')
+        assert e == 'Entry(fooentry)', e
+
+        e = env.Entry('${BAR}_$BAR')
+        assert e == 'Entry(barentry_barentry)', e
+
+        e = env.Entry(['entry1'])
+        assert e == ['Entry(entry1)'], e
+
+        e = env.Entry(['entry1', 'entry2'])
+        assert e == ['Entry(entry1)', 'Entry(entry2)'], e
+
     def test_File(self):
         """Test the File() method"""
         class MyFS:
             def File(self, name):
                 return 'File(%s)' % name
 
     def test_File(self):
         """Test the File() method"""
         class MyFS:
             def File(self, name):
                 return 'File(%s)' % name
 
-        env = Environment(FOO = 'foofile', BAR = 'barfile')
+        env = self.TestEnvironment(FOO = 'foofile', BAR = 'barfile')
         env.fs = MyFS()
 
         f = env.File('f')
         env.fs = MyFS()
 
         f = env.File('f')
@@ -2301,9 +3012,15 @@ f5: \
         f = env.File('${BAR}_$BAR')
         assert f == 'File(barfile_barfile)', f
 
         f = env.File('${BAR}_$BAR')
         assert f == 'File(barfile_barfile)', f
 
+        f = env.File(['file1'])
+        assert f == ['File(file1)'], f
+
+        f = env.File(['file1', 'file2'])
+        assert f == ['File(file1)', 'File(file2)'], f
+
     def test_FindFile(self):
         """Test the FindFile() method"""
     def test_FindFile(self):
         """Test the FindFile() method"""
-        env = Environment(FOO = 'fff', BAR = 'bbb')
+        env = self.TestEnvironment(FOO = 'fff', BAR = 'bbb')
 
         r = env.FindFile('foo', ['no_such_directory'])
         assert r is None, r
 
         r = env.FindFile('foo', ['no_such_directory'])
         assert r is None, r
@@ -2320,7 +3037,7 @@ f5: \
 
     def test_GetBuildPath(self):
         """Test the GetBuildPath() method."""
 
     def test_GetBuildPath(self):
         """Test the GetBuildPath() method."""
-        env = Environment(MAGIC = 'xyzzy')
+        env = self.TestEnvironment(MAGIC = 'xyzzy')
 
         p = env.GetBuildPath('foo')
         assert p == 'foo', p
 
         p = env.GetBuildPath('foo')
         assert p == 'foo', p
@@ -2330,7 +3047,7 @@ f5: \
 
     def test_Ignore(self):
         """Test the explicit Ignore method."""
 
     def test_Ignore(self):
         """Test the explicit Ignore method."""
-        env = Environment(FOO='yyy', BAR='zzz')
+        env = self.TestEnvironment(FOO='yyy', BAR='zzz')
         env.Dir('dir1')
         env.Dir('dir2')
         env.File('yyyzzz')
         env.Dir('dir1')
         env.Dir('dir2')
         env.File('yyyzzz')
@@ -2360,75 +3077,9 @@ f5: \
         assert i.__class__.__name__ == 'Dir', i.__class__.__name__
         assert i.path == 'dir2'
 
         assert i.__class__.__name__ == 'Dir', i.__class__.__name__
         assert i.path == 'dir2'
 
-    def test_Install(self):
-       """Test the Install method"""
-        env = Environment(FOO='iii', BAR='jjj')
-
-        tgt = env.Install('export', [ 'build/foo1', 'build/foo2' ])
-        paths = map(str, tgt)
-        paths.sort()
-        expect = map(os.path.normpath, [ 'export/foo1', 'export/foo2' ])
-        assert paths == expect, paths
-        for tnode in tgt:
-            assert tnode.builder == InstallBuilder
-
-        tgt = env.Install('$FOO', [ 'build/${BAR}1', 'build/${BAR}2' ])
-        paths = map(str, tgt)
-        paths.sort()
-        expect = map(os.path.normpath, [ 'iii/jjj1', 'iii/jjj2' ])
-        assert paths == expect, paths
-        for tnode in tgt:
-            assert tnode.builder == InstallBuilder
-
-        exc_caught = None
-        try:
-            tgt = env.Install('export', 'export')
-        except SCons.Errors.UserError, e:
-            exc_caught = 1
-        assert exc_caught, "UserError should be thrown when Install() target is not a file."
-        match = str(e) == "Source `export' of Install() is not a file.  Install() source must be one or more files."
-        assert match, e
-
-        exc_caught = None
-        try:
-            tgt = env.Install('export', ['export', 'build/foo1'])
-        except SCons.Errors.UserError, e:
-            exc_caught = 1
-        assert exc_caught, "UserError should be thrown when Install() target containins non-files."
-        match = str(e) == "Source `['export', 'build/foo1']' of Install() contains one or more non-files.  Install() source must be one or more files."
-        assert match, e
-
-        exc_caught = None
-        try:
-            tgt = env.Install('export/foo1', 'build/foo1')
-        except SCons.Errors.UserError, e:
-            exc_caught = 1
-        assert exc_caught, "UserError should be thrown reversing the order of Install() targets."
-        match = str(e) == "Target `export/foo1' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?"
-        assert match, e
-
-    def test_InstallAs(self):
-       """Test the InstallAs method"""
-        env = Environment(FOO='iii', BAR='jjj')
-
-        tgt = env.InstallAs(target=string.split('foo1 foo2'),
-                            source=string.split('bar1 bar2'))
-        assert len(tgt) == 2, len(tgt)
-        paths = map(lambda x: str(x.sources[0]), tgt)
-        paths.sort()
-        expect = map(os.path.normpath, [ 'bar1', 'bar2' ])
-        assert paths == expect, paths
-        for tnode in tgt:
-            assert tnode.builder == InstallBuilder
-
-        tgt = env.InstallAs(target='${FOO}.t', source='${BAR}.s')[0]
-        assert tgt.path == 'iii.t'
-        assert tgt.sources[0].path == 'jjj.s'
-        assert tgt.builder == InstallBuilder
-
     def test_Literal(self):
         """Test the Literal() method"""
     def test_Literal(self):
         """Test the Literal() method"""
-        env = Environment(FOO='fff', BAR='bbb')
+        env = self.TestEnvironment(FOO='fff', BAR='bbb')
         list = env.subst_list([env.Literal('$FOO'), '$BAR'])[0]
         assert list == ['$FOO', 'bbb'], list
         list = env.subst_list(['$FOO', env.Literal('$BAR')])[0]
         list = env.subst_list([env.Literal('$FOO'), '$BAR'])[0]
         assert list == ['$FOO', 'bbb'], list
         list = env.subst_list(['$FOO', env.Literal('$BAR')])[0]
@@ -2436,7 +3087,7 @@ f5: \
 
     def test_Local(self):
         """Test the Local() method."""
 
     def test_Local(self):
         """Test the Local() method."""
-        env = Environment(FOO='lll')
+        env = self.TestEnvironment(FOO='lll')
 
         l = env.Local(env.fs.File('fff'))
         assert str(l[0]) == 'fff', l[0]
 
         l = env.Local(env.fs.File('fff'))
         assert str(l[0]) == 'fff', l[0]
@@ -2447,7 +3098,7 @@ f5: \
 
     def test_Precious(self):
         """Test the Precious() method"""
 
     def test_Precious(self):
         """Test the Precious() method"""
-        env = Environment(FOO='ggg', BAR='hhh')
+        env = self.TestEnvironment(FOO='ggg', BAR='hhh')
         env.Dir('p_hhhb')
         env.File('p_d')
         t = env.Precious('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO')
         env.Dir('p_hhhb')
         env.File('p_d')
         t = env.Precious('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO')
@@ -2477,7 +3128,7 @@ f5: \
                 self.list.extend(list(dirs))
             def Dir(self, name):
                 return name
                 self.list.extend(list(dirs))
             def Dir(self, name):
                 return name
-        env = Environment(FOO='rrr', BAR='sss')
+        env = self.TestEnvironment(FOO='rrr', BAR='sss')
         env.fs = MyFS()
         env.Repository('/tmp/foo')
         env.Repository('/tmp/$FOO', '/tmp/$BAR/foo')
         env.fs = MyFS()
         env.Repository('/tmp/foo')
         env.Repository('/tmp/$FOO', '/tmp/$BAR/foo')
@@ -2489,20 +3140,20 @@ f5: \
         def scan(node, env, target, arg):
             pass
 
         def scan(node, env, target, arg):
             pass
 
-        env = Environment(FOO = scan)
+        env = self.TestEnvironment(FOO = scan)
 
         s = env.Scanner('foo')
 
         s = env.Scanner('foo')
-        assert not s is None, s
+        assert s is not None, s
 
         s = env.Scanner(function = 'foo')
 
         s = env.Scanner(function = 'foo')
-        assert not s is None, s
+        assert s is not None, s
 
         if 0:
             s = env.Scanner('$FOO')
 
         if 0:
             s = env.Scanner('$FOO')
-            assert not s is None, s
+            assert s is not None, s
 
             s = env.Scanner(function = '$FOO')
 
             s = env.Scanner(function = '$FOO')
-            assert not s is None, s
+            assert s is not None, s
 
     def test_SConsignFile(self):
         """Test the SConsignFile() method"""
 
     def test_SConsignFile(self):
         """Test the SConsignFile() method"""
@@ -2511,9 +3162,10 @@ f5: \
         class MyFS:
             SConstruct_dir = os.sep + 'dir'
 
         class MyFS:
             SConstruct_dir = os.sep + 'dir'
 
-        env = Environment(FOO = 'SConsign',
+        env = self.TestEnvironment(FOO = 'SConsign',
                           BAR = os.path.join(os.sep, 'File'))
         env.fs = MyFS()
                           BAR = os.path.join(os.sep, 'File'))
         env.fs = MyFS()
+        env.Execute = lambda action: None
 
         try:
             fnames = []
 
         try:
             fnames = []
@@ -2526,34 +3178,42 @@ f5: \
             SCons.SConsign.File = capture
 
             env.SConsignFile('foo')
             SCons.SConsign.File = capture
 
             env.SConsignFile('foo')
-            assert fnames[0] == os.path.join(os.sep, 'dir', 'foo'), fnames
-            assert dbms[0] == None, dbms
+            assert fnames[-1] == os.path.join(os.sep, 'dir', 'foo'), fnames
+            assert dbms[-1] is None, dbms
 
             env.SConsignFile('$FOO')
 
             env.SConsignFile('$FOO')
-            assert fnames[1] == os.path.join(os.sep, 'dir', 'SConsign'), fnames
-            assert dbms[1] == None, dbms
+            assert fnames[-1] == os.path.join(os.sep, 'dir', 'SConsign'), fnames
+            assert dbms[-1] is None, dbms
 
             env.SConsignFile('/$FOO')
 
             env.SConsignFile('/$FOO')
-            assert fnames[2] == '/SConsign', fnames
-            assert dbms[2] == None, dbms
+            assert fnames[-1] == os.sep + 'SConsign', fnames
+            assert dbms[-1] is None, dbms
+
+            env.SConsignFile(os.sep + '$FOO')
+            assert fnames[-1] == os.sep + 'SConsign', fnames
+            assert dbms[-1] is None, dbms
 
             env.SConsignFile('$BAR', 'x')
 
             env.SConsignFile('$BAR', 'x')
-            assert fnames[3] == os.path.join(os.sep, 'File'), fnames
-            assert dbms[3] == 'x', dbms
+            assert fnames[-1] == os.path.join(os.sep, 'File'), fnames
+            assert dbms[-1] == 'x', dbms
 
             env.SConsignFile('__$BAR', 7)
 
             env.SConsignFile('__$BAR', 7)
-            assert fnames[4] == os.path.join(os.sep, 'dir', '__', 'File'), fnames
-            assert dbms[4] == 7, dbms
+            assert fnames[-1] == os.path.join(os.sep, 'dir', '__', 'File'), fnames
+            assert dbms[-1] == 7, dbms
 
             env.SConsignFile()
 
             env.SConsignFile()
-            assert fnames[5] == os.path.join(os.sep, 'dir', '.sconsign'), fnames
-            assert dbms[5] == None, dbms
+            assert fnames[-1] == os.path.join(os.sep, 'dir', '.sconsign'), fnames
+            assert dbms[-1] is None, dbms
+
+            env.SConsignFile(None)
+            assert fnames[-1] is None, fnames
+            assert dbms[-1] is None, dbms
         finally:
             SCons.SConsign.File = save_SConsign_File
 
     def test_SideEffect(self):
         """Test the SideEffect() method"""
         finally:
             SCons.SConsign.File = save_SConsign_File
 
     def test_SideEffect(self):
         """Test the SideEffect() method"""
-        env = Environment(LIB='lll', FOO='fff', BAR='bbb')
+        env = self.TestEnvironment(LIB='lll', FOO='fff', BAR='bbb')
         env.File('mylll.pdb')
         env.Dir('mymmm.pdb')
 
         env.File('mylll.pdb')
         env.Dir('mymmm.pdb')
 
@@ -2565,8 +3225,6 @@ f5: \
         assert s.side_effect
         assert foo.side_effects == [s]
         assert bar.side_effects == [s]
         assert s.side_effect
         assert foo.side_effects == [s]
         assert bar.side_effects == [s]
-        assert s.depends_on([bar])
-        assert s.depends_on([foo])
 
         fff = env.Object('fff.obj', 'fff.cpp')[0]
         bbb = env.Object('bbb.obj', 'bbb.cpp')[0]
 
         fff = env.Object('fff.obj', 'fff.cpp')[0]
         bbb = env.Object('bbb.obj', 'bbb.cpp')[0]
@@ -2576,8 +3234,6 @@ f5: \
         assert s.side_effect
         assert fff.side_effects == [s], fff.side_effects
         assert bbb.side_effects == [s], bbb.side_effects
         assert s.side_effect
         assert fff.side_effects == [s], fff.side_effects
         assert bbb.side_effects == [s], bbb.side_effects
-        assert s.depends_on([bbb])
-        assert s.depends_on([fff])
 
         ggg = env.Object('ggg.obj', 'ggg.cpp')[0]
         ccc = env.Object('ccc.obj', 'ccc.cpp')[0]
 
         ggg = env.Object('ggg.obj', 'ggg.cpp')[0]
         ccc = env.Object('ccc.obj', 'ccc.cpp')[0]
@@ -2587,12 +3243,10 @@ f5: \
         assert s.side_effect
         assert ggg.side_effects == [s], ggg.side_effects
         assert ccc.side_effects == [s], ccc.side_effects
         assert s.side_effect
         assert ggg.side_effects == [s], ggg.side_effects
         assert ccc.side_effects == [s], ccc.side_effects
-        assert s.depends_on([ccc])
-        assert s.depends_on([ggg])
 
     def test_SourceCode(self):
         """Test the SourceCode() method."""
 
     def test_SourceCode(self):
         """Test the SourceCode() method."""
-        env = Environment(FOO='mmm', BAR='nnn')
+        env = self.TestEnvironment(FOO='mmm', BAR='nnn')
         e = env.SourceCode('foo', None)[0]
         assert e.path == 'foo'
         s = e.src_builder()
         e = env.SourceCode('foo', None)[0]
         assert e.path == 'foo'
         s = e.src_builder()
@@ -2611,7 +3265,9 @@ f5: \
 
     def test_SourceSignatures(type):
         """Test the SourceSignatures() method"""
 
     def test_SourceSignatures(type):
         """Test the SourceSignatures() method"""
-        env = Environment(M = 'MD5', T = 'timestamp')
+        import SCons.Errors
+
+        env = type.TestEnvironment(M = 'MD5', T = 'timestamp')
 
         exc_caught = None
         try:
 
         exc_caught = None
         try:
@@ -2619,23 +3275,35 @@ f5: \
         except SCons.Errors.UserError:
             exc_caught = 1
         assert exc_caught, "did not catch expected UserError"
         except SCons.Errors.UserError:
             exc_caught = 1
         assert exc_caught, "did not catch expected UserError"
-        assert not hasattr(env, '_calc_module')
 
         env.SourceSignatures('MD5')
 
         env.SourceSignatures('MD5')
-        m = env._calc_module
+        assert env.src_sig_type == 'MD5', env.src_sig_type
 
         env.SourceSignatures('$M')
 
         env.SourceSignatures('$M')
-        assert env._calc_module is m
+        assert env.src_sig_type == 'MD5', env.src_sig_type
 
         env.SourceSignatures('timestamp')
 
         env.SourceSignatures('timestamp')
-        t = env._calc_module
+        assert env.src_sig_type == 'timestamp', env.src_sig_type
 
         env.SourceSignatures('$T')
 
         env.SourceSignatures('$T')
-        assert env._calc_module is t
+        assert env.src_sig_type == 'timestamp', env.src_sig_type
+
+        try:
+            import SCons.Util
+            save_md5 = SCons.Util.md5
+            SCons.Util.md5 = None
+            try:
+                env.SourceSignatures('MD5')
+            except SCons.Errors.UserError:
+                pass
+            else:
+                self.fail('Did not catch expected UserError')
+        finally:
+            SCons.Util.md5 = save_md5
 
     def test_Split(self):
         """Test the Split() method"""
 
     def test_Split(self):
         """Test the Split() method"""
-        env = Environment(FOO='fff', BAR='bbb')
+        env = self.TestEnvironment(FOO='fff', BAR='bbb')
         s = env.Split("foo bar")
         assert s == ["foo", "bar"], s
         s = env.Split("$FOO bar")
         s = env.Split("foo bar")
         assert s == ["foo", "bar"], s
         s = env.Split("$FOO bar")
@@ -2651,7 +3319,9 @@ f5: \
 
     def test_TargetSignatures(type):
         """Test the TargetSignatures() method"""
 
     def test_TargetSignatures(type):
         """Test the TargetSignatures() method"""
-        env = Environment(B = 'build', C = 'content')
+        import SCons.Errors
+
+        env = type.TestEnvironment(B = 'build', C = 'content')
 
         exc_caught = None
         try:
 
         exc_caught = None
         try:
@@ -2662,16 +3332,41 @@ f5: \
         assert not hasattr(env, '_build_signature')
 
         env.TargetSignatures('build')
         assert not hasattr(env, '_build_signature')
 
         env.TargetSignatures('build')
-        assert env._build_signature == 1, env._build_signature
-
-        env.TargetSignatures('content')
-        assert env._build_signature == 0, env._build_signature
+        assert env.tgt_sig_type == 'build', env.tgt_sig_type
 
         env.TargetSignatures('$B')
 
         env.TargetSignatures('$B')
-        assert env._build_signature == 1, env._build_signature
+        assert env.tgt_sig_type == 'build', env.tgt_sig_type
+
+        env.TargetSignatures('content')
+        assert env.tgt_sig_type == 'content', env.tgt_sig_type
 
         env.TargetSignatures('$C')
 
         env.TargetSignatures('$C')
-        assert env._build_signature == 0, env._build_signature
+        assert env.tgt_sig_type == 'content', env.tgt_sig_type
+
+        env.TargetSignatures('MD5')
+        assert env.tgt_sig_type == 'MD5', env.tgt_sig_type
+
+        env.TargetSignatures('timestamp')
+        assert env.tgt_sig_type == 'timestamp', env.tgt_sig_type
+
+        try:
+            import SCons.Util
+            save_md5 = SCons.Util.md5
+            SCons.Util.md5 = None
+            try:
+                env.TargetSignatures('MD5')
+            except SCons.Errors.UserError:
+                pass
+            else:
+                self.fail('Did not catch expected UserError')
+            try:
+                env.TargetSignatures('content')
+            except SCons.Errors.UserError:
+                pass
+            else:
+                self.fail('Did not catch expected UserError')
+        finally:
+            SCons.Util.md5 = save_md5
 
     def test_Value(self):
         """Test creating a Value() object
 
     def test_Value(self):
         """Test creating a Value() object
@@ -2688,6 +3383,9 @@ f5: \
         assert not v1 is v2
         assert v1.value == v2.value
 
         assert not v1 is v2
         assert v1.value == v2.value
 
+        v3 = env.Value('c', 'build-c')
+        assert v3.value == 'c', v3.value
+
 
 
     def test_Environment_global_variable(type):
 
 
     def test_Environment_global_variable(type):
@@ -2706,114 +3404,182 @@ f5: \
         f = env.xxx('$FOO')
         assert f == 'foo', f
 
         f = env.xxx('$FOO')
         assert f == 'foo', f
 
-    def test_bad_keywords(type):
+    def test_bad_keywords(self):
         """Test trying to use reserved keywords in an Environment"""
         """Test trying to use reserved keywords in an Environment"""
-        reserved = ['TARGETS','SOURCES', 'SOURCE','TARGET']
         added = []
 
         added = []
 
-        env = SCons.Environment.Environment(TARGETS = 'targets',
-                                            SOURCES = 'sources',
-                                            SOURCE = 'source',
-                                            TARGET = 'target',
-                                            INIT = 'init')
+        env = self.TestEnvironment(TARGETS = 'targets',
+                                   SOURCES = 'sources',
+                                   SOURCE = 'source',
+                                   TARGET = 'target',
+                                   CHANGED_SOURCES = 'changed_sources',
+                                   CHANGED_TARGETS = 'changed_targets',
+                                   UNCHANGED_SOURCES = 'unchanged_sources',
+                                   UNCHANGED_TARGETS = 'unchanged_targets',
+                                   INIT = 'init')
+        bad_msg = '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'
         added.append('INIT')
         added.append('INIT')
-        for x in reserved:
-            assert not env.has_key(x), env[x]
+        for x in self.reserved_variables:
+            assert x not in env, env[x]
         for x in added:
         for x in added:
-            assert env.has_key(x), \
-                   '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'%x
+            assert x in env, bad_msg % x
 
         env.Append(TARGETS = 'targets',
                    SOURCES = 'sources',
                    SOURCE = 'source',
                    TARGET = 'target',
 
         env.Append(TARGETS = 'targets',
                    SOURCES = 'sources',
                    SOURCE = 'source',
                    TARGET = 'target',
+                   CHANGED_SOURCES = 'changed_sources',
+                   CHANGED_TARGETS = 'changed_targets',
+                   UNCHANGED_SOURCES = 'unchanged_sources',
+                   UNCHANGED_TARGETS = 'unchanged_targets',
                    APPEND = 'append')
         added.append('APPEND')
                    APPEND = 'append')
         added.append('APPEND')
-        for x in reserved:
-            assert not env.has_key(x), env[x]
+        for x in self.reserved_variables:
+            assert x not in env, env[x]
         for x in added:
         for x in added:
-            assert env.has_key(x), \
-                   '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'%x
+            assert x in env, bad_msg % x
 
         env.AppendUnique(TARGETS = 'targets',
                          SOURCES = 'sources',
                          SOURCE = 'source',
                          TARGET = 'target',
 
         env.AppendUnique(TARGETS = 'targets',
                          SOURCES = 'sources',
                          SOURCE = 'source',
                          TARGET = 'target',
+                         CHANGED_SOURCES = 'changed_sources',
+                         CHANGED_TARGETS = 'changed_targets',
+                         UNCHANGED_SOURCES = 'unchanged_sources',
+                         UNCHANGED_TARGETS = 'unchanged_targets',
                          APPENDUNIQUE = 'appendunique')
         added.append('APPENDUNIQUE')
                          APPENDUNIQUE = 'appendunique')
         added.append('APPENDUNIQUE')
-        for x in reserved:
-            assert not env.has_key(x), env[x]
+        for x in self.reserved_variables:
+            assert x not in env, env[x]
         for x in added:
         for x in added:
-            assert env.has_key(x), \
-                   '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'%x
+            assert x in env, bad_msg % x
 
         env.Prepend(TARGETS = 'targets',
                     SOURCES = 'sources',
                     SOURCE = 'source',
                     TARGET = 'target',
 
         env.Prepend(TARGETS = 'targets',
                     SOURCES = 'sources',
                     SOURCE = 'source',
                     TARGET = 'target',
+                    CHANGED_SOURCES = 'changed_sources',
+                    CHANGED_TARGETS = 'changed_targets',
+                    UNCHANGED_SOURCES = 'unchanged_sources',
+                    UNCHANGED_TARGETS = 'unchanged_targets',
                     PREPEND = 'prepend')
         added.append('PREPEND')
                     PREPEND = 'prepend')
         added.append('PREPEND')
-        for x in reserved:
-            assert not env.has_key(x), env[x]
+        for x in self.reserved_variables:
+            assert x not in env, env[x]
         for x in added:
         for x in added:
-            assert env.has_key(x), \
-                   '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'%x
+            assert x in env, bad_msg % x
 
         env.Prepend(TARGETS = 'targets',
                     SOURCES = 'sources',
                     SOURCE = 'source',
                     TARGET = 'target',
 
         env.Prepend(TARGETS = 'targets',
                     SOURCES = 'sources',
                     SOURCE = 'source',
                     TARGET = 'target',
+                    CHANGED_SOURCES = 'changed_sources',
+                    CHANGED_TARGETS = 'changed_targets',
+                    UNCHANGED_SOURCES = 'unchanged_sources',
+                    UNCHANGED_TARGETS = 'unchanged_targets',
                     PREPENDUNIQUE = 'prependunique')
         added.append('PREPENDUNIQUE')
                     PREPENDUNIQUE = 'prependunique')
         added.append('PREPENDUNIQUE')
-        for x in reserved:
-            assert not env.has_key(x), env[x]
+        for x in self.reserved_variables:
+            assert x not in env, env[x]
         for x in added:
         for x in added:
-            assert env.has_key(x), \
-                   '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'%x
+            assert x in env, bad_msg % x
 
         env.Replace(TARGETS = 'targets',
                     SOURCES = 'sources',
                     SOURCE = 'source',
                     TARGET = 'target',
 
         env.Replace(TARGETS = 'targets',
                     SOURCES = 'sources',
                     SOURCE = 'source',
                     TARGET = 'target',
+                    CHANGED_SOURCES = 'changed_sources',
+                    CHANGED_TARGETS = 'changed_targets',
+                    UNCHANGED_SOURCES = 'unchanged_sources',
+                    UNCHANGED_TARGETS = 'unchanged_targets',
                     REPLACE = 'replace')
         added.append('REPLACE')
                     REPLACE = 'replace')
         added.append('REPLACE')
-        for x in reserved:
-            assert not env.has_key(x), env[x]
+        for x in self.reserved_variables:
+            assert x not in env, env[x]
         for x in added:
         for x in added:
-            assert env.has_key(x), \
-                   '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'%x
-
-        copy = env.Copy(TARGETS = 'targets',
-                        SOURCES = 'sources',
-                        SOURCE = 'source',
-                        TARGET = 'target',
-                        COPY = 'copy')
-        for x in reserved:
-            assert not copy.has_key(x), env[x]
+            assert x in env, bad_msg % x
+
+        copy = env.Clone(TARGETS = 'targets',
+                         SOURCES = 'sources',
+                         SOURCE = 'source',
+                         TARGET = 'target',
+                         CHANGED_SOURCES = 'changed_sources',
+                         CHANGED_TARGETS = 'changed_targets',
+                         UNCHANGED_SOURCES = 'unchanged_sources',
+                         UNCHANGED_TARGETS = 'unchanged_targets',
+                         COPY = 'copy')
+        for x in self.reserved_variables:
+            assert x not in copy, env[x]
         for x in added + ['COPY']:
         for x in added + ['COPY']:
-            assert copy.has_key(x), \
-                   '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'%x
+            assert x in copy, bad_msg % x
 
         over = env.Override({'TARGETS' : 'targets',
                              'SOURCES' : 'sources',
                              'SOURCE' : 'source',
                              'TARGET' : 'target',
 
         over = env.Override({'TARGETS' : 'targets',
                              'SOURCES' : 'sources',
                              'SOURCE' : 'source',
                              'TARGET' : 'target',
+                             'CHANGED_SOURCES' : 'changed_sources',
+                             'CHANGED_TARGETS' : 'changed_targets',
+                             'UNCHANGED_SOURCES' : 'unchanged_sources',
+                             'UNCHANGED_TARGETS' : 'unchanged_targets',
                              'OVERRIDE' : 'override'})
                              'OVERRIDE' : 'override'})
-        for x in reserved:
-            assert not over.has_key(x), over[x]
+        for x in self.reserved_variables:
+            assert x not in over, over[x]
         for x in added + ['OVERRIDE']:
         for x in added + ['OVERRIDE']:
-            assert over.has_key(x), \
-                   '%s is not reserved, but got omitted; see Environment.construction_var_name_ok'%x
+            assert x in over, bad_msg % x
+
+    def test_parse_flags(self):
+        '''Test the Base class parse_flags argument'''
+        # all we have to show is that it gets to MergeFlags internally
+        env = Environment(tools=[], parse_flags = '-X')
+        assert env['CCFLAGS'] == ['-X'], env['CCFLAGS']
+
+        env = Environment(tools=[], CCFLAGS=None, parse_flags = '-Y')
+        assert env['CCFLAGS'] == ['-Y'], env['CCFLAGS']
+
+        env = Environment(tools=[], CPPDEFINES = 'FOO', parse_flags = '-std=c99 -X -DBAR')
+        assert env['CFLAGS']  == ['-std=c99'], env['CFLAGS']
+        assert env['CCFLAGS'] == ['-X'], env['CCFLAGS']
+        assert env['CPPDEFINES'] == ['FOO', 'BAR'], env['CPPDEFINES']
+
+    def test_clone_parse_flags(self):
+        '''Test the env.Clone() parse_flags argument'''
+        # all we have to show is that it gets to MergeFlags internally
+        env = Environment(tools = [])
+        env2 = env.Clone(parse_flags = '-X')
+        assert 'CCFLAGS' not in env
+        assert env2['CCFLAGS'] == ['-X'], env2['CCFLAGS']
+
+        env = Environment(tools = [], CCFLAGS=None)
+        env2 = env.Clone(parse_flags = '-Y')
+        assert env['CCFLAGS'] is None, env['CCFLAGS']
+        assert env2['CCFLAGS'] == ['-Y'], env2['CCFLAGS']
 
 
+        env = Environment(tools = [], CPPDEFINES = 'FOO')
+        env2 = env.Clone(parse_flags = '-std=c99 -X -DBAR')
+        assert 'CFLAGS' not in env
+        assert env2['CFLAGS']  == ['-std=c99'], env2['CFLAGS']
+        assert 'CCFLAGS' not in env
+        assert env2['CCFLAGS'] == ['-X'], env2['CCFLAGS']
+        assert env['CPPDEFINES'] == 'FOO', env['CPPDEFINES']
+        assert env2['CPPDEFINES'] == ['FOO','BAR'], env2['CPPDEFINES']
 
 
 
 
-class OverrideEnvironmentTestCase(unittest.TestCase):
+
+class OverrideEnvironmentTestCase(unittest.TestCase,TestEnvironmentFixture):
+
+    def setUp(self):
+        env = Environment()
+        env._dict = {'XXX' : 'x', 'YYY' : 'y'}
+        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
+        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'})
+        self.envs = [ env, env2, env3 ]
+
+    def checkpath(self, node, expect):
+        return str(node) == os.path.normpath(expect)
 
     def test___init__(self):
         """Test OverrideEnvironment initialization"""
 
     def test___init__(self):
         """Test OverrideEnvironment initialization"""
-        env = Environment(XXX = 'x', YYY = 'y')
-        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
-        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3'})
+        env, env2, env3 = self.envs
         assert env['XXX'] == 'x', env['XXX']
         assert env2['XXX'] == 'x2', env2['XXX']
         assert env3['XXX'] == 'x3', env3['XXX']
         assert env['XXX'] == 'x', env['XXX']
         assert env2['XXX'] == 'x2', env2['XXX']
         assert env3['XXX'] == 'x3', env3['XXX']
@@ -2821,77 +3587,115 @@ class OverrideEnvironmentTestCase(unittest.TestCase):
         assert env2['YYY'] == 'y', env2['YYY']
         assert env3['YYY'] == 'y3', env3['YYY']
 
         assert env2['YYY'] == 'y', env2['YYY']
         assert env3['YYY'] == 'y3', env3['YYY']
 
+    def test___delitem__(self):
+        """Test deleting variables from an OverrideEnvironment"""
+        env, env2, env3 = self.envs
+
+        del env3['XXX']
+        assert 'XXX' not in env, "env has XXX?"
+        assert 'XXX' not in env2, "env2 has XXX?"
+        assert 'XXX' not in env3, "env3 has XXX?"
+
+        del env3['YYY']
+        assert 'YYY' not in env, "env has YYY?"
+        assert 'YYY' not in env2, "env2 has YYY?"
+        assert 'YYY' not in env3, "env3 has YYY?"
+
+        del env3['ZZZ']
+        assert 'ZZZ' not in env, "env has ZZZ?"
+        assert 'ZZZ' not in env2, "env2 has ZZZ?"
+        assert 'ZZZ' not in env3, "env3 has ZZZ?"
+
     def test_get(self):
         """Test the OverrideEnvironment get() method"""
     def test_get(self):
         """Test the OverrideEnvironment get() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
-        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
-        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'})
+        env, env2, env3 = self.envs
         assert env.get('XXX') == 'x', env.get('XXX')
         assert env2.get('XXX') == 'x2', env2.get('XXX')
         assert env3.get('XXX') == 'x3', env3.get('XXX')
         assert env.get('YYY') == 'y', env.get('YYY')
         assert env2.get('YYY') == 'y', env2.get('YYY')
         assert env3.get('YYY') == 'y3', env3.get('YYY')
         assert env.get('XXX') == 'x', env.get('XXX')
         assert env2.get('XXX') == 'x2', env2.get('XXX')
         assert env3.get('XXX') == 'x3', env3.get('XXX')
         assert env.get('YYY') == 'y', env.get('YYY')
         assert env2.get('YYY') == 'y', env2.get('YYY')
         assert env3.get('YYY') == 'y3', env3.get('YYY')
-        assert env.get('ZZZ') == None, env.get('ZZZ')
-        assert env2.get('ZZZ') == None, env2.get('ZZZ')
+        assert env.get('ZZZ') is None, env.get('ZZZ')
+        assert env2.get('ZZZ') is None, env2.get('ZZZ')
         assert env3.get('ZZZ') == 'z3', env3.get('ZZZ')
 
     def test_has_key(self):
         """Test the OverrideEnvironment has_key() method"""
         assert env3.get('ZZZ') == 'z3', env3.get('ZZZ')
 
     def test_has_key(self):
         """Test the OverrideEnvironment has_key() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
-        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
-        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'})
-        assert env.has_key('XXX'), env.has_key('XXX')
-        assert env2.has_key('XXX'), env2.has_key('XXX')
-        assert env3.has_key('XXX'), env3.has_key('XXX')
-        assert env.has_key('YYY'), env.has_key('YYY')
-        assert env2.has_key('YYY'), env2.has_key('YYY')
-        assert env3.has_key('YYY'), env3.has_key('YYY')
-        assert not env.has_key('ZZZ'), env.has_key('ZZZ')
-        assert not env2.has_key('ZZZ'), env2.has_key('ZZZ')
-        assert env3.has_key('ZZZ'), env3.has_key('ZZZ')
+        env, env2, env3 = self.envs
+        assert 'XXX' in env, 'XXX' in env
+        assert 'XXX' in env2, 'XXX' in env2
+        assert 'XXX' in env3, 'XXX' in env3
+        assert 'YYY' in env, 'YYY' in env
+        assert 'YYY' in env2, 'YYY' in env2
+        assert 'YYY' in env3, 'YYY' in env3
+        assert 'ZZZ' not in env, 'ZZZ' in env
+        assert 'ZZZ' not in env2, 'ZZZ' in env2
+        assert 'ZZZ' in env3, 'ZZZ' in env3
+
+    def test_contains(self):
+        """Test the OverrideEnvironment __contains__() method"""
+        try:
+            'x' in {'x':1}
+        except TypeError:
+            # TODO(1.5)
+            # An early version of Python that doesn't support "in"
+            # on dictionaries.  Just pass the test.
+            pass
+        else:
+            env, env2, env3 = self.envs
+            assert 'XXX' in env
+            assert 'XXX' in env2
+            assert 'XXX' in env3
+            assert 'YYY' in env
+            assert 'YYY' in env2
+            assert 'YYY' in env3
+            assert not 'ZZZ' in env
+            assert not 'ZZZ' in env2
+            assert 'ZZZ' in env3
+
+    def test_items(self):
+        """Test the OverrideEnvironment Dictionary() method"""
+        env, env2, env3 = self.envs
+        items = env.Dictionary()
+        assert items == {'XXX' : 'x', 'YYY' : 'y'}, items
+        items = env2.Dictionary()
+        assert items == {'XXX' : 'x2', 'YYY' : 'y'}, items
+        items = env3.Dictionary()
+        assert items == {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'}, items
 
     def test_items(self):
         """Test the OverrideEnvironment items() method"""
 
     def test_items(self):
         """Test the OverrideEnvironment items() method"""
-        env = Environment(WWW = 'w', XXX = 'x', YYY = 'y')
-        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
-        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'})
-        items = env.items()
-        assert items == {'WWW' : 'w', 'XXX' : 'x', 'YYY' : 'y'}, items
-        items = env2.items()
-        assert items == {'WWW' : 'w', 'XXX' : 'x2', 'YYY' : 'y'}, items
-        items = env3.items()
-        assert items == {'WWW' : 'w', 'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'}, items
+        env, env2, env3 = self.envs
+        items = sorted(env.items())
+        assert items == [('XXX', 'x'), ('YYY', 'y')], items
+        items = sorted(env2.items())
+        assert items == [('XXX', 'x2'), ('YYY', 'y')], items
+        items = sorted(env3.items())
+        assert items == [('XXX', 'x3'), ('YYY', 'y3'), ('ZZZ', 'z3')], items
 
     def test_gvars(self):
         """Test the OverrideEnvironment gvars() method"""
 
     def test_gvars(self):
         """Test the OverrideEnvironment gvars() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
-        env2 = OverrideEnvironment(env, {'xxx' : 'x2'})
-        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3'})
+        env, env2, env3 = self.envs
         gvars = env.gvars()
         assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars
         gvars = env2.gvars()
         gvars = env.gvars()
         assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars
         gvars = env2.gvars()
-        assert gvars == {'XXX' : 'x2', 'YYY' : 'y'}, gvars
+        assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars
         gvars = env3.gvars()
         gvars = env3.gvars()
-        assert gvars == {'XXX' : 'x3', 'YYY' : 'y3'}, gvars
+        assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars
 
     def test_lvars(self):
         """Test the OverrideEnvironment lvars() method"""
 
     def test_lvars(self):
         """Test the OverrideEnvironment lvars() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
-        env2 = OverrideEnvironment(env, {'xxx' : 'x2'})
-        env3 = OverrideEnvironment(env2, {'xxx' : 'x3', 'YYY' : 'y3'})
+        env, env2, env3 = self.envs
         lvars = env.lvars()
         assert lvars == {}, lvars
         lvars = env2.lvars()
         lvars = env.lvars()
         assert lvars == {}, lvars
         lvars = env2.lvars()
-        assert lvars == {'XXX' : 'x2', 'YYY' : 'y'}, lvars
+        assert lvars == {'XXX' : 'x2'}, lvars
         lvars = env3.lvars()
         lvars = env3.lvars()
-        assert lvars == {'XXX' : 'x3', 'YYY' : 'y3'}, lvars
+        assert lvars == {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'}, lvars
 
     def test_Replace(self):
         """Test the OverrideEnvironment Replace() method"""
 
     def test_Replace(self):
         """Test the OverrideEnvironment Replace() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
-        env2 = OverrideEnvironment(env, {'xxx' : 'x2'})
-        env3 = OverrideEnvironment(env2, {'xxx' : 'x3', 'YYY' : 'y3'})
+        env, env2, env3 = self.envs
         assert env['XXX'] == 'x', env['XXX']
         assert env2['XXX'] == 'x2', env2['XXX']
         assert env3['XXX'] == 'x3', env3['XXX']
         assert env['XXX'] == 'x', env['XXX']
         assert env2['XXX'] == 'x2', env2['XXX']
         assert env3['XXX'] == 'x3', env3['XXX']
@@ -2908,14 +3712,132 @@ class OverrideEnvironmentTestCase(unittest.TestCase):
         assert env2['YYY'] == 'y4', env2['YYY']
         assert env3['YYY'] == 'y3', env3['YYY']
 
         assert env2['YYY'] == 'y4', env2['YYY']
         assert env3['YYY'] == 'y3', env3['YYY']
 
+    # Tests a number of Base methods through an OverrideEnvironment to
+    # make sure they handle overridden constructionv variables properly.
+    #
+    # The following Base methods also call self.subst(), and so could
+    # theoretically be subject to problems with evaluating overridden
+    # variables, but they're never really called that way in the rest
+    # of our code, so we won't worry about them (at least for now):
+    #
+    # ParseConfig()
+    # ParseDepends()
+    # Platform()
+    # Tool()
+    #
+    # Action()
+    # Alias()
+    # Builder()
+    # CacheDir()
+    # Configure()
+    # Environment()
+    # FindFile()
+    # Scanner()
+    # SourceSignatures()
+    # TargetSignatures()
+
+    # It's unlikely Clone() will ever be called this way, so let the
+    # other methods test that handling overridden values works.
+    #def test_Clone(self):
+    #    """Test the OverrideEnvironment Clone() method"""
+    #    pass
 
 
+    def test_FindIxes(self):
+        """Test the OverrideEnvironment FindIxes() method"""
+        env, env2, env3 = self.envs
+        x = env.FindIxes(['xaaay'], 'XXX', 'YYY')
+        assert x == 'xaaay', x
+        x = env2.FindIxes(['x2aaay'], 'XXX', 'YYY')
+        assert x == 'x2aaay', x
+        x = env3.FindIxes(['x3aaay3'], 'XXX', 'YYY')
+        assert x == 'x3aaay3', x
 
 
+    def test_ReplaceIxes(self):
+        """Test the OverrideEnvironment ReplaceIxes() method"""
+        env, env2, env3 = self.envs
+        x = env.ReplaceIxes('xaaay', 'XXX', 'YYY', 'YYY', 'XXX')
+        assert x == 'yaaax', x
+        x = env2.ReplaceIxes('x2aaay', 'XXX', 'YYY', 'YYY', 'XXX')
+        assert x == 'yaaax2', x
+        x = env3.ReplaceIxes('x3aaay3', 'XXX', 'YYY', 'YYY', 'XXX')
+        assert x == 'y3aaax3', x
+
+    # It's unlikely WhereIs() will ever be called this way, so let the
+    # other methods test that handling overridden values works.
+    #def test_WhereIs(self):
+    #    """Test the OverrideEnvironment WhereIs() method"""
+    #    pass
 
 
-class NoSubstitutionProxyTestCase(unittest.TestCase):
+    def test_Dir(self):
+        """Test the OverrideEnvironment Dir() method"""
+        env, env2, env3 = self.envs
+        x = env.Dir('ddir/$XXX')
+        assert self.checkpath(x, 'ddir/x'), str(x)
+        x = env2.Dir('ddir/$XXX')
+        assert self.checkpath(x, 'ddir/x2'), str(x)
+        x = env3.Dir('ddir/$XXX')
+        assert self.checkpath(x, 'ddir/x3'), str(x)
+
+    def test_Entry(self):
+        """Test the OverrideEnvironment Entry() method"""
+        env, env2, env3 = self.envs
+        x = env.Entry('edir/$XXX')
+        assert self.checkpath(x, 'edir/x'), str(x)
+        x = env2.Entry('edir/$XXX')
+        assert self.checkpath(x, 'edir/x2'), str(x)
+        x = env3.Entry('edir/$XXX')
+        assert self.checkpath(x, 'edir/x3'), str(x)
+
+    def test_File(self):
+        """Test the OverrideEnvironment File() method"""
+        env, env2, env3 = self.envs
+        x = env.File('fdir/$XXX')
+        assert self.checkpath(x, 'fdir/x'), str(x)
+        x = env2.File('fdir/$XXX')
+        assert self.checkpath(x, 'fdir/x2'), str(x)
+        x = env3.File('fdir/$XXX')
+        assert self.checkpath(x, 'fdir/x3'), str(x)
+
+    def test_Split(self):
+        """Test the OverrideEnvironment Split() method"""
+        env, env2, env3 = self.envs
+        env['AAA'] = '$XXX $YYY $ZZZ'
+        x = env.Split('$AAA')
+        assert x == ['x', 'y'], x
+        x = env2.Split('$AAA')
+        assert x == ['x2', 'y'], x
+        x = env3.Split('$AAA')
+        assert x == ['x3', 'y3', 'z3'], x
+
+    def test_parse_flags(self):
+        '''Test the OverrideEnvironment parse_flags argument'''
+        # all we have to show is that it gets to MergeFlags internally
+        env = SubstitutionEnvironment()
+        env2 = env.Override({'parse_flags' : '-X'})
+        assert 'CCFLAGS' not in env
+        assert env2['CCFLAGS'] == ['-X'], env2['CCFLAGS']
+
+        env = SubstitutionEnvironment(CCFLAGS=None)
+        env2 = env.Override({'parse_flags' : '-Y'})
+        assert env['CCFLAGS'] is None, env['CCFLAGS']
+        assert env2['CCFLAGS'] == ['-Y'], env2['CCFLAGS']
+
+        env = SubstitutionEnvironment(CPPDEFINES = 'FOO')
+        env2 = env.Override({'parse_flags' : '-std=c99 -X -DBAR'})
+        assert 'CFLAGS' not in env
+        assert env2['CFLAGS']  == ['-std=c99'], env2['CFLAGS']
+        assert 'CCFLAGS' not in env
+        assert env2['CCFLAGS'] == ['-X'], env2['CCFLAGS']
+        assert env['CPPDEFINES'] == 'FOO', env['CPPDEFINES']
+        assert env2['CPPDEFINES'] == ['FOO','BAR'], env2['CPPDEFINES']
+
+
+
+class NoSubstitutionProxyTestCase(unittest.TestCase,TestEnvironmentFixture):
 
     def test___init__(self):
         """Test NoSubstitutionProxy initialization"""
 
     def test___init__(self):
         """Test NoSubstitutionProxy initialization"""
-        env = Environment(XXX = 'x', YYY = 'y')
+        env = self.TestEnvironment(XXX = 'x', YYY = 'y')
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
@@ -2943,7 +3865,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     def test_subst(self):
         """Test the NoSubstitutionProxy.subst() method"""
 
     def test_subst(self):
         """Test the NoSubstitutionProxy.subst() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
+        env = self.TestEnvironment(XXX = 'x', YYY = 'y')
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
@@ -2963,7 +3885,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     def test_subst_kw(self):
         """Test the NoSubstitutionProxy.subst_kw() method"""
 
     def test_subst_kw(self):
         """Test the NoSubstitutionProxy.subst_kw() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
+        env = self.TestEnvironment(XXX = 'x', YYY = 'y')
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
@@ -2978,7 +3900,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     def test_subst_list(self):
         """Test the NoSubstitutionProxy.subst_list() method"""
 
     def test_subst_list(self):
         """Test the NoSubstitutionProxy.subst_list() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
+        env = self.TestEnvironment(XXX = 'x', YYY = 'y')
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
@@ -2996,7 +3918,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     def test_subst_target_source(self):
         """Test the NoSubstitutionProxy.subst_target_source() method"""
 
     def test_subst_target_source(self):
         """Test the NoSubstitutionProxy.subst_target_source() method"""
-        env = Environment(XXX = 'x', YYY = 'y')
+        env = self.TestEnvironment(XXX = 'x', YYY = 'y')
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
         assert env['XXX'] == 'x', env['XXX']
         assert env['YYY'] == 'y', env['YYY']
 
@@ -3006,20 +3928,61 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
         args = ('$XXX $TARGET $SOURCE $YYY',)
         kw = {'target' : DummyNode('ttt'), 'source' : DummyNode('sss')}
 
         args = ('$XXX $TARGET $SOURCE $YYY',)
         kw = {'target' : DummyNode('ttt'), 'source' : DummyNode('sss')}
-        x = apply(env.subst_target_source, args, kw)
+        x = env.subst_target_source(*args, **kw)
         assert x == 'x ttt sss y', x
         assert x == 'x ttt sss y', x
-        x = apply(proxy.subst_target_source, args, kw)
+        x = proxy.subst_target_source(*args, **kw)
         assert x == ' ttt sss ', x
 
         assert x == ' ttt sss ', x
 
+class EnvironmentVariableTestCase(unittest.TestCase):
+
+    def test_is_valid_construction_var(self):
+        """Testing is_valid_construction_var()"""
+        r = is_valid_construction_var("_a")
+        assert r is not None, r
+        r = is_valid_construction_var("z_")
+        assert r is not None, r
+        r = is_valid_construction_var("X_")
+        assert r is not None, r
+        r = is_valid_construction_var("2a")
+        assert r is None, r
+        r = is_valid_construction_var("a2_")
+        assert r is not None, r
+        r = is_valid_construction_var("/")
+        assert r is None, r
+        r = is_valid_construction_var("_/")
+        assert r is None, r
+        r = is_valid_construction_var("a/")
+        assert r is None, r
+        r = is_valid_construction_var(".b")
+        assert r is None, r
+        r = is_valid_construction_var("_.b")
+        assert r is None, r
+        r = is_valid_construction_var("b1._")
+        assert r is None, r
+        r = is_valid_construction_var("-b")
+        assert r is None, r
+        r = is_valid_construction_var("_-b")
+        assert r is None, r
+        r = is_valid_construction_var("b1-_")
+        assert r is None, r
+
 
 
 if __name__ == "__main__":
     suite = unittest.TestSuite()
     tclasses = [ SubstitutionTestCase,
                  BaseTestCase,
 
 
 if __name__ == "__main__":
     suite = unittest.TestSuite()
     tclasses = [ SubstitutionTestCase,
                  BaseTestCase,
-                 NoSubstitutionProxyTestCase ]
+                 OverrideEnvironmentTestCase,
+                 NoSubstitutionProxyTestCase,
+                 EnvironmentVariableTestCase ]
     for tclass in tclasses:
         names = unittest.getTestCaseNames(tclass, 'test_')
     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)
     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: