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__"
 
+import SCons.compat
+
+import copy
 import os
-import string
+import StringIO
 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():
-       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"
-        elif env2.has_key(k):
+        elif k in env2:
            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():
-       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"
-        elif env2.has_key(k):
+        elif k in env2:
            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 = {}
 
-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.
     """
@@ -129,9 +128,13 @@ class Scanner:
 
 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)
+    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))
 
@@ -147,7 +150,29 @@ class DummyNode:
     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):
 
@@ -155,7 +180,7 @@ class SubstitutionTestCase(unittest.TestCase):
         """Test initializing a SubstitutionEnvironment
         """
         env = SubstitutionEnvironment()
-        assert not env.has_key('__env__')
+        assert '__env__' not in env
 
     def test___cmp__(self):
         """Test comparing SubstitutionEnvironments
@@ -179,13 +204,13 @@ class SubstitutionTestCase(unittest.TestCase):
         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):
-        """Test deleting a variable from a SubstitutionEnvironment
+        """Test setting a variable in a SubstitutionEnvironment
         """
         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')
-        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
@@ -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):
-            if not dict.has_key(name):
+            if name not in dict:
                 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"
 
-        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
@@ -334,6 +375,22 @@ class SubstitutionTestCase(unittest.TestCase):
         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()
@@ -366,6 +423,16 @@ class SubstitutionTestCase(unittest.TestCase):
         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')
@@ -493,9 +560,13 @@ class SubstitutionTestCase(unittest.TestCase):
                 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
@@ -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', '$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'))
-        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'))
-        assert map(str, r) == ['foo', 'sss', 'bar'], r
+        assert list(map(str, r)) == ['foo', 'sss', 'bar'], r
 
         n = MyObj()
 
@@ -527,13 +601,13 @@ class SubstitutionTestCase(unittest.TestCase):
                           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}" ])
-        assert r == [ "bar/foo", "baz/bar" ]
+        assert r == [ "bar/foo", "baz/bar" ], r
 
         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"""
@@ -541,6 +615,128 @@ class SubstitutionTestCase(unittest.TestCase):
         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)
@@ -566,9 +762,140 @@ class SubstitutionTestCase(unittest.TestCase):
         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
@@ -576,16 +903,34 @@ class BaseTestCase(unittest.TestCase):
         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 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."""
-        env = Environment(aaa = 'AAA')
+        env = self.TestEnvironment(aaa = 'AAA')
 
         x = env.get('aaa')
         assert x == 'AAA', x
@@ -609,53 +954,80 @@ class BaseTestCase(unittest.TestCase):
                                  '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['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['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.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 = {}
-        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')
@@ -671,6 +1043,8 @@ class BaseTestCase(unittest.TestCase):
         assert built_it['out1']
         assert built_it['out2']
 
+
+
     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"])
+        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 = {}
-#        env1 = Environment(SCANNERS = s1)
+#        env1 = self.TestEnvironment(SCANNERS = s1)
 #        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']
 #
@@ -708,64 +1083,126 @@ class BaseTestCase(unittest.TestCase):
 
         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])
-        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])
-        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])
-        s = map(env.get_scanner, suffixes)
+        s = list(map(env.get_scanner, suffixes))
         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]
-        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])
-        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])
-        s = map(env.get_scanner, suffixes)
+        s = list(map(env.get_scanner, suffixes))
         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):
-       """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):
-        """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()
-            for kw in ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
+            for kw in self.reserved_variables:
                 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
-                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)
 
@@ -788,128 +1225,91 @@ class BaseTestCase(unittest.TestCase):
     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',
-                          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]
-        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]
-        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]
-        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')
-        assert len(l[0]) == 0, l[0]
+        assert l == [[]], l
         l = env.subst_list('$_F77INCFLAGS')
-        assert len(l[0]) == 0, l[0]
+        assert l == [[]], l
         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]
-        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."""
@@ -921,7 +1321,7 @@ class BaseTestCase(unittest.TestCase):
             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"
@@ -943,7 +1343,7 @@ class BaseTestCase(unittest.TestCase):
             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"
@@ -960,7 +1360,7 @@ class BaseTestCase(unittest.TestCase):
             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
@@ -977,7 +1377,7 @@ def exists(env):
     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']
@@ -1015,20 +1415,20 @@ def exists(env):
             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
-        env = Environment(tools = [None], XYZ = 'xyz')
+        env = self.TestEnvironment(tools = [None], XYZ = 'xyz')
         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()"
-        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
@@ -1043,7 +1443,7 @@ def exists(env):
 
     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']
@@ -1051,7 +1451,7 @@ def exists(env):
 
     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']
@@ -1079,6 +1479,8 @@ def exists(env):
         b2 = Environment()['BUILDERS']
         assert b1 == b2, diff_dict(b1, b2)
 
+        import UserDict
+        UD = UserDict.UserDict
         import UserList
         UL = UserList.UserList
 
@@ -1110,6 +1512,18 @@ def exists(env):
             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']),
@@ -1160,14 +1574,21 @@ def exists(env):
         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
-                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
+            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
 
@@ -1193,80 +1614,140 @@ def exists(env):
 
         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']
 
-        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']
 
-        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."""
-        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 = ';')
+        # 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')
 
+        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."""
-        env = Environment(AAA1 = 'a1',
+        env = self.TestEnvironment(AAA1 = 'a1',
                           AAA2 = 'a2',
                           AAA3 = 'a3',
+                          AAA4 = 'a4',
+                          AAA5 = 'a5',
                           BBB1 = ['b1'],
                           BBB2 = ['b2'],
                           BBB3 = ['b3'],
+                          BBB4 = ['b4'],
+                          BBB5 = ['b5'],
                           CCC1 = '',
-                          CCC2 = '')
+                          CCC2 = '',
+                          DDD1 = ['a', 'b', 'c'])
         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'],
-                         BBB3 = ['b3', 'c', 'd', 'b3'],
+                         BBB3 = ['b3', 'c', 'd', 'c', 'b3'],
+                         BBB4 = 'b4.new',
+                         BBB5 = ['b5.new'],
                          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['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['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['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).
-        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.
         """
-        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')
@@ -1274,7 +1755,7 @@ def exists(env):
         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'
@@ -1285,38 +1766,38 @@ def exists(env):
         # 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 })
-        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')
-        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 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 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 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
-        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
@@ -1330,8 +1811,8 @@ def exists(env):
 
         # 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
@@ -1340,14 +1821,68 @@ def exists(env):
 
         # 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
-        env2 = env1.Copy()
+        env2 = env1.Clone()
         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 = '')
@@ -1359,14 +1894,14 @@ def exists(env):
             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")
 
-            env = Environment(ENV = { 'PATH' : [sub1, sub2] })
+            env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] })
 
             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")
 
-            env = Environment(ENV = { 'PATH' : [sub1, sub2] })
+            env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] })
 
             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)
 
-            env = Environment(ENV = { 'PATH' : [sub1, sub2] })
+            env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] })
 
             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
 
-        env = Environment(ENV = { 'PATH' : [] })
+        env = self.TestEnvironment(ENV = { 'PATH' : [] })
         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()"
-        env = Environment(LIBPREFIX='lib',
+        env = self.TestEnvironment(LIBPREFIX='lib',
                           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 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')
-        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"""
-        env = Environment(ASFLAGS='assembler',
-                          COMMAND='command',
+        env = self.TestEnvironment(COMMAND='command',
+                          ASFLAGS='assembler',
+                          CCFLAGS=[''],
+                          CPPDEFINES=[],
                           CPPFLAGS=[''],
                           CPPPATH='string',
+                          FRAMEWORKPATH=[],
+                          FRAMEWORKS=[],
                           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)
-                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 = []
-            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 " + \
-                                 "-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
-            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['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['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:
-            os.popen = orig_popen
+            env.backtick = orig_backtick
 
     def test_ParseDepends(self):
         """Test the ParseDepends() method"""
@@ -1523,7 +2080,7 @@ f5: \
    mno \
 """)
 
-        env = Environment(SINGLE = test.workpath('single'))
+        env = self.TestEnvironment(SINGLE = test.workpath('single'))
 
         tlist = []
         dlist = []
@@ -1546,8 +2103,8 @@ f5: \
         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
 
@@ -1555,8 +2112,8 @@ f5: \
         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
 
@@ -1569,7 +2126,7 @@ f5: \
 
     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:
@@ -1594,6 +2151,8 @@ f5: \
     def test_Prepend(self):
         """Test prepending to construction variables in an Environment
         """
+        import UserDict
+        UD = UserDict.UserDict
         import UserList
         UL = UserList.UserList
 
@@ -1625,6 +2184,18 @@ f5: \
             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']),
@@ -1675,14 +2246,21 @@ f5: \
         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
-                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
+            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
 
@@ -1698,91 +2276,142 @@ f5: \
         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']
 
-        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."""
-        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 = ';')
+        # 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')
 
-    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."""
-        env = Environment(AAA1 = 'a1',
+        env = self.TestEnvironment(AAA1 = 'a1',
                           AAA2 = 'a2',
                           AAA3 = 'a3',
+                          AAA4 = 'a4',
+                          AAA5 = 'a5',
                           BBB1 = ['b1'],
                           BBB2 = ['b2'],
                           BBB3 = ['b3'],
+                          BBB4 = ['b4'],
+                          BBB5 = ['b5'],
                           CCC1 = '',
-                          CCC2 = '')
+                          CCC2 = '',
+                          DDD1 = ['a', 'b', 'c'])
         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'],
+                          BBB4 = 'b4.new',
+                          BBB5 = ['b5.new'],
                           CCC1 = 'c1',
-                          CCC2 = ['c2'])
+                          CCC2 = ['c2'],
+                          DDD1 = 'b')
         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['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['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.
         """
-        env1 = Environment(AAA = 'a', BBB = 'b')
+        env1 = self.TestEnvironment(AAA = 'a', BBB = 'b')
         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)
 
-        env3 = Environment(BUILDERS = {'b1' : 1})
+        b1 = Builder()
+        b2 = Builder()
+        env3 = self.TestEnvironment(BUILDERS = {'b1' : b1})
         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()"
-        env = Environment(LIBPREFIX='lib',
+        env = self.TestEnvironment(LIBPREFIX='lib',
                           LIBSUFFIX='.a',
                           SHLIBPREFIX='lib',
                           SHLIBSUFFIX='.so',
@@ -1803,7 +2432,7 @@ f5: \
 
     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
@@ -1813,21 +2442,21 @@ f5: \
 
     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')
-        except SCons.Errors.UserError:
+        except SCons.Errors.EnvironmentError:
             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')
-        except SCons.Errors.UserError:
+        except SCons.Errors.EnvironmentError:
             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.
@@ -1837,6 +2466,29 @@ f5: \
         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 = '')
@@ -1865,21 +2517,21 @@ f5: \
                           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'),
-                        ] + 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', 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)
@@ -1887,13 +2539,13 @@ f5: \
         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', 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':
@@ -1904,13 +2556,13 @@ f5: \
             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')
-            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
 
-        env = Environment(FOO = 'xyzzy')
+        env = self.TestEnvironment(FOO = 'xyzzy')
 
         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
-        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
-        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
-        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"""
-        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]
@@ -1951,7 +2607,7 @@ f5: \
 
     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]
@@ -1962,7 +2618,7 @@ f5: \
 
     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
@@ -1979,37 +2635,37 @@ f5: \
 
         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
-        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
-        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
-        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]
-        s = map(str, tgt.sources)
+        s = list(map(str, tgt.sources))
         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]
@@ -2024,85 +2680,87 @@ f5: \
 
     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[1].__class__.__name__ == 'File'
+        assert t[1].__class__.__name__ == 'Entry'
         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[3].__class__.__name__ == 'File'
+        assert t[3].__class__.__name__ == 'Entry'
         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
-
-    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
-             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
 
-        env = Environment(FOO = 'fff', BAR = 'bbb')
+        env = self.TestEnvironment(FOO = 'fff', BAR = 'bbb')
         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
 
-        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"""
-        env = Environment(FOO = 'xyzzy')
+        env = self.TestEnvironment(FOO = 'xyzzy')
 
         b = env.Builder(action = 'foo')
-        assert not b is None, b
+        assert b is not None, b
 
         b = env.Builder(action = '$FOO')
-        assert not b is None, b
+        assert b is not None, b
 
         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)
-        assert not b is None, b
+        assert b is not None, b
         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"""
-        class MyFS:
-            def CacheDir(self, path):
-                self.CD = path
-
-        env = Environment(CD = 'CacheDir')
-        env.fs = MyFS()
+        env = self.TestEnvironment(CD = 'CacheDir')
 
         env.CacheDir('foo')
-        assert env.fs.CD == 'foo', env.fs.CD
+        assert env._CacheDir_path == 'foo', env._CacheDir_path
 
         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"""
-        env = Environment(FOO = 'fff', BAR = 'bbb')
+        env = self.TestEnvironment(FOO = 'fff', BAR = 'bbb')
 
         CT = SCons.Environment.CleanTargets
 
@@ -2110,20 +2768,20 @@ f5: \
         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'])
-        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')
-        l = map(str, CT[fff])
+        l = list(map(str, CT[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):
@@ -2131,37 +2789,37 @@ f5: \
         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 '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]
-        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'
-            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]
-        assert not t.builder is None
+        assert t.builder is not None
         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)
-        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]
-        assert not t.builder is None
+        assert t.builder is not None
         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)
-        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]
@@ -2186,17 +2844,17 @@ f5: \
         try:
             os.chdir(test.workpath())
 
-            env = Environment(FOO = 'xyzzy')
+            env = self.TestEnvironment(FOO = 'xyzzy')
 
             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})
-            assert not c is None, c
+            assert c is not None, c
             assert hasattr(c, 'foo')
             assert hasattr(c, 'xyzzy')
             c.Finish()
@@ -2205,7 +2863,7 @@ f5: \
 
     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')
@@ -2241,7 +2899,7 @@ f5: \
             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')
@@ -2253,16 +2911,45 @@ f5: \
         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"""
 
-        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"""
-        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']
@@ -2283,13 +2970,37 @@ f5: \
         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
 
-        env = Environment(FOO = 'foofile', BAR = 'barfile')
+        env = self.TestEnvironment(FOO = 'foofile', BAR = 'barfile')
         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(['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"""
-        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
@@ -2320,7 +3037,7 @@ f5: \
 
     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
@@ -2330,7 +3047,7 @@ f5: \
 
     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')
@@ -2360,75 +3077,9 @@ f5: \
         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"""
-        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]
@@ -2436,7 +3087,7 @@ f5: \
 
     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]
@@ -2447,7 +3098,7 @@ f5: \
 
     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')
@@ -2477,7 +3128,7 @@ f5: \
                 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')
@@ -2489,20 +3140,20 @@ f5: \
         def scan(node, env, target, arg):
             pass
 
-        env = Environment(FOO = scan)
+        env = self.TestEnvironment(FOO = scan)
 
         s = env.Scanner('foo')
-        assert not s is None, s
+        assert s is not None, s
 
         s = env.Scanner(function = 'foo')
-        assert not s is None, s
+        assert s is not None, s
 
         if 0:
             s = env.Scanner('$FOO')
-            assert not s is None, s
+            assert s is not None, s
 
             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"""
@@ -2511,9 +3162,10 @@ f5: \
         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()
+        env.Execute = lambda action: None
 
         try:
             fnames = []
@@ -2526,34 +3178,42 @@ f5: \
             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')
-            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')
-            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')
-            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)
-            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()
-            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"""
-        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')
 
@@ -2565,8 +3225,6 @@ f5: \
         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]
@@ -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.depends_on([bbb])
-        assert s.depends_on([fff])
 
         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.depends_on([ccc])
-        assert s.depends_on([ggg])
 
     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()
@@ -2611,7 +3265,9 @@ f5: \
 
     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:
@@ -2619,23 +3275,35 @@ f5: \
         except SCons.Errors.UserError:
             exc_caught = 1
         assert exc_caught, "did not catch expected UserError"
-        assert not hasattr(env, '_calc_module')
 
         env.SourceSignatures('MD5')
-        m = env._calc_module
+        assert env.src_sig_type == 'MD5', env.src_sig_type
 
         env.SourceSignatures('$M')
-        assert env._calc_module is m
+        assert env.src_sig_type == 'MD5', env.src_sig_type
 
         env.SourceSignatures('timestamp')
-        t = env._calc_module
+        assert env.src_sig_type == 'timestamp', env.src_sig_type
 
         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"""
-        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")
@@ -2651,7 +3319,9 @@ f5: \
 
     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:
@@ -2662,16 +3332,41 @@ f5: \
         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')
-        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')
-        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
@@ -2688,6 +3383,9 @@ f5: \
         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):
@@ -2706,114 +3404,182 @@ f5: \
         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"""
-        reserved = ['TARGETS','SOURCES', 'SOURCE','TARGET']
         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')
-        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:
-            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',
+                   CHANGED_SOURCES = 'changed_sources',
+                   CHANGED_TARGETS = 'changed_targets',
+                   UNCHANGED_SOURCES = 'unchanged_sources',
+                   UNCHANGED_TARGETS = 'unchanged_targets',
                    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:
-            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',
+                         CHANGED_SOURCES = 'changed_sources',
+                         CHANGED_TARGETS = 'changed_targets',
+                         UNCHANGED_SOURCES = 'unchanged_sources',
+                         UNCHANGED_TARGETS = 'unchanged_targets',
                          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:
-            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',
+                    CHANGED_SOURCES = 'changed_sources',
+                    CHANGED_TARGETS = 'changed_targets',
+                    UNCHANGED_SOURCES = 'unchanged_sources',
+                    UNCHANGED_TARGETS = 'unchanged_targets',
                     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:
-            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',
+                    CHANGED_SOURCES = 'changed_sources',
+                    CHANGED_TARGETS = 'changed_targets',
+                    UNCHANGED_SOURCES = 'unchanged_sources',
+                    UNCHANGED_TARGETS = 'unchanged_targets',
                     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:
-            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',
+                    CHANGED_SOURCES = 'changed_sources',
+                    CHANGED_TARGETS = 'changed_targets',
+                    UNCHANGED_SOURCES = 'unchanged_sources',
+                    UNCHANGED_TARGETS = 'unchanged_targets',
                     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:
-            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']:
-            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',
+                             'CHANGED_SOURCES' : 'changed_sources',
+                             'CHANGED_TARGETS' : 'changed_targets',
+                             'UNCHANGED_SOURCES' : 'unchanged_sources',
+                             'UNCHANGED_TARGETS' : 'unchanged_targets',
                              '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']:
-            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"""
-        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']
@@ -2821,77 +3587,115 @@ class OverrideEnvironmentTestCase(unittest.TestCase):
         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"""
-        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('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"""
-        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"""
-        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"""
-        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()
-        assert gvars == {'XXX' : 'x2', 'YYY' : 'y'}, gvars
+        assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, 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"""
-        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()
-        assert lvars == {'XXX' : 'x2', 'YYY' : 'y'}, lvars
+        assert lvars == {'XXX' : 'x2'}, 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"""
-        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']
@@ -2908,14 +3712,132 @@ class OverrideEnvironmentTestCase(unittest.TestCase):
         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"""
-        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']
 
@@ -2943,7 +3865,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     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']
 
@@ -2963,7 +3885,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     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']
 
@@ -2978,7 +3900,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     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']
 
@@ -2996,7 +3918,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     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']
 
@@ -3006,20 +3928,61 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
         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
-        x = apply(proxy.subst_target_source, args, kw)
+        x = proxy.subst_target_source(*args, **kw)
         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,
-                 NoSubstitutionProxyTestCase ]
+                 OverrideEnvironmentTestCase,
+                 NoSubstitutionProxyTestCase,
+                 EnvironmentVariableTestCase ]
     for tclass in tclasses:
         names = unittest.getTestCaseNames(tclass, 'test_')
-        suite.addTests(map(tclass, names))
+        suite.addTests(list(map(tclass, names)))
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
         sys.exit(1)
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: