http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / EnvironmentTests.py
index b2b17e146343336cfeb7db6e518a2d0733994631..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,15 +79,17 @@ 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.
     """
     def __init__(self, name = None):
         self.name = name
 
-    def __call__(self, env, **kw):
+    def __call__(self, env, target=None, source=None, **kw):
         global called_it
+        called_it['target'] = target
+        called_it['source'] = source
         called_it.update(kw)
 
     def execute(self, target = None, **kw):
@@ -108,18 +109,32 @@ class Scanner:
         self.skeys = skeys
 
     def __call__(self, filename):
+        global scanned_it
         scanned_it[filename] = 1
 
     def __cmp__(self, other):
-        return cmp(self.__dict__, other.__dict__)
+        try:
+            return cmp(self.__dict__, other.__dict__)
+        except AttributeError:
+            return 1
+
+    def get_skeys(self, env):
+        return self.skeys
+
+    def __str__(self):
+        return self.name
 
 
 
 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))
 
@@ -135,45 +150,118 @@ 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):
 
+    def test___init__(self):
+        """Test initializing a SubstitutionEnvironment
+        """
+        env = SubstitutionEnvironment()
+        assert '__env__' not in env
 
-class EnvironmentTestCase(unittest.TestCase):
+    def test___cmp__(self):
+        """Test comparing SubstitutionEnvironments
+        """
 
-    def test___init__(self):
-        """Test construction Environment creation
+        env1 = SubstitutionEnvironment(XXX = 'x')
+        env2 = SubstitutionEnvironment(XXX = 'x')
+        env3 = SubstitutionEnvironment(XXX = 'xxx')
+        env4 = SubstitutionEnvironment(XXX = 'x', YYY = 'x')
 
-        Create two with identical arguments and check that
-        they compare the same.
+        assert env1 == env2
+        assert env1 != env3
+        assert env1 != env4
+
+    def test___delitem__(self):
+        """Test deleting a variable from a SubstitutionEnvironment
         """
-        env1 = Environment(XXX = 'x', YYY = 'y')
-        env2 = Environment(XXX = 'x', YYY = 'y')
-        assert env1 == env2, diff_env(env1, env2)
+        env1 = SubstitutionEnvironment(XXX = 'x', YYY = 'y')
+        env2 = SubstitutionEnvironment(XXX = 'x')
+        del env1['YYY']
+        assert env1 == env2
 
-        assert env1['__env__'] is env1, env1['__env__']
-        assert env2['__env__'] is env2, env2['__env__']
+    def test___getitem__(self):
+        """Test fetching a variable from a SubstitutionEnvironment
+        """
+        env = SubstitutionEnvironment(XXX = 'x')
+        assert env['XXX'] == 'x', env['XXX']
+
+    def test___setitem__(self):
+        """Test setting a variable in a SubstitutionEnvironment
+        """
+        env1 = SubstitutionEnvironment(XXX = 'x')
+        env2 = SubstitutionEnvironment(XXX = 'x', YYY = 'y')
+        env1['YYY'] = 'y'
+        assert env1 == env2
 
     def test_get(self):
-        """Test the get() method."""
-        env = Environment(aaa = 'AAA')
+        """Test the SubstitutionEnvironment get() method
+        """
+        env = SubstitutionEnvironment(XXX = 'x')
+        assert env.get('XXX') == 'x', env.get('XXX')
+        assert env.get('YYY') is None, env.get('YYY')
 
-        x = env.get('aaa')
-        assert x == 'AAA', x
-        x = env.get('aaa', 'XXX')
-        assert x == 'AAA', x
-        x = env.get('bbb')
-        assert x is None, x
-        x = env.get('bbb', 'XXX')
-        assert x == 'XXX', x
+    def test_has_key(self):
+        """Test the SubstitutionEnvironment has_key() method
+        """
+        env = SubstitutionEnvironment(XXX = 'x')
+        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
+        """
+        env = SubstitutionEnvironment(XXX = 'x', YYY = 'y')
+        items = env.items()
+        assert items == [('XXX','x'), ('YYY','y')], items
 
     def test_arg2nodes(self):
         """Test the arg2nodes method
         """
-        env = Environment()
+        env = SubstitutionEnvironment()
         dict = {}
         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]
@@ -183,8 +271,9 @@ class EnvironmentTestCase(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
@@ -236,7 +325,7 @@ class EnvironmentTestCase(unittest.TestCase):
             else:
                 return None
 
-        env_ll = env.Copy()
+        env_ll = SubstitutionEnvironment()
         env_ll.lookup_list = [lookup_a, lookup_b]
 
         nodes = env_ll.arg2nodes(['aaa', 'bbb', 'ccc'], Factory)
@@ -286,53 +375,89 @@ class EnvironmentTestCase(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()
+        gvars = env.gvars()
+        assert gvars == {}, gvars
+
+    def test_lvars(self):
+        """Test the base class lvars() method"""
+        env = SubstitutionEnvironment()
+        lvars = env.lvars()
+        assert lvars == {}, lvars
+
     def test_subst(self):
         """Test substituting construction variables within strings
 
         Check various combinations, including recursive expansion
         of variables into other variables.
         """
-        env = Environment(AAA = 'a', BBB = 'b')
+        env = SubstitutionEnvironment(AAA = 'a', BBB = 'b')
         mystr = env.subst("$AAA ${AAA}A $BBBB $BBB")
         assert mystr == "a aA b", mystr
 
         # Changed the tests below to reflect a bug fix in
         # subst()
-        env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
         mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
         assert mystr == "b bA bB b", mystr
 
-        env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
         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')
         s2 = DummyNode('s2')
 
-        env = Environment(AAA = 'aaa')
+        env = SubstitutionEnvironment(AAA = 'aaa')
         s = env.subst('$AAA $TARGET $SOURCES', target=[t1, t2], source=[s1, s2])
         assert s == "aaa t1 s1 s2", s
         s = env.subst('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2])
         assert s == "aaa t1 t2 s1", s
-        s = env.subst('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2], dict={})
-        assert s == "aaa", s
 
-        # Test callables in the Environment
+        # Test callables in the SubstitutionEnvironment
         def foo(target, source, env, for_signature):
             assert str(target) == 't', target
             assert str(source) == 's', source
             return env["FOO"]
 
-        env = Environment(BAR=foo, FOO='baz')
+        env = SubstitutionEnvironment(BAR=foo, FOO='baz')
         t = DummyNode('t')
         s = DummyNode('s')
 
         subst = env.subst('test $BAR', target=t, source=s)
         assert subst == 'test baz', subst
 
-        # Test not calling callables in the Environment
+        # Test not calling callables in the SubstitutionEnvironment
         if 0:
             # This will take some serious surgery to subst() and
             # subst_list(), so just leave these tests out until we can
@@ -340,7 +465,7 @@ class EnvironmentTestCase(unittest.TestCase):
             def bar(arg):
                 pass
 
-            env = Environment(BAR=bar, FOO='$BAR')
+            env = SubstitutionEnvironment(BAR=bar, FOO='$BAR')
 
             subst = env.subst('$BAR', call=None)
             assert subst is bar, subst
@@ -349,8 +474,8 @@ class EnvironmentTestCase(unittest.TestCase):
             assert subst is bar, subst
 
     def test_subst_kw(self):
-       """Test substituting construction variables within dictionaries"""
-       env = Environment(AAA = 'a', BBB = 'b')
+        """Test substituting construction variables within dictionaries"""
+        env = SubstitutionEnvironment(AAA = 'a', BBB = 'b')
         kw = env.subst_kw({'$AAA' : 'aaa', 'bbb' : '$BBB'})
         assert len(kw) == 2, kw
         assert kw['a'] == 'aaa', kw['a']
@@ -359,21 +484,21 @@ class EnvironmentTestCase(unittest.TestCase):
     def test_subst_list(self):
         """Test substituting construction variables in command lists
         """
-        env = Environment(AAA = 'a', BBB = 'b')
+        env = SubstitutionEnvironment(AAA = 'a', BBB = 'b')
         l = env.subst_list("$AAA ${AAA}A $BBBB $BBB")
         assert l == [["a", "aA", "b"]], l
 
         # Changed the tests below to reflect a bug fix in
         # subst()
-        env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
         l = env.subst_list("$AAA ${AAA}A ${AAA}B $BBB")
         assert l == [["b", "bA", "bB", "b"]], l
 
-        env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
         l = env.subst_list("$AAA ${AAA}A ${AAA}B $BBB")
         assert l == [["c", "cA", "cB", "c"]], mystr
 
-        env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ])
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ])
         lst = env.subst_list([ "$AAA", "B $CCC" ])
         assert lst == [[ "a", "b"], ["c", "B a", "b"], ["c"]], lst
 
@@ -382,28 +507,26 @@ class EnvironmentTestCase(unittest.TestCase):
         s1 = DummyNode('s1')
         s2 = DummyNode('s2')
 
-        env = Environment(AAA = 'aaa')
+        env = SubstitutionEnvironment(AAA = 'aaa')
         s = env.subst_list('$AAA $TARGET $SOURCES', target=[t1, t2], source=[s1, s2])
         assert s == [["aaa", "t1", "s1", "s2"]], s
         s = env.subst_list('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2])
         assert s == [["aaa", "t1", "t2", "s1"]], s
-        s = env.subst_list('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2], dict={})
-        assert s == [["aaa"]], s
 
-        # Test callables in the Environment
+        # Test callables in the SubstitutionEnvironment
         def foo(target, source, env, for_signature):
             assert str(target) == 't', target
             assert str(source) == 's', source
             return env["FOO"]
 
-        env = Environment(BAR=foo, FOO='baz')
+        env = SubstitutionEnvironment(BAR=foo, FOO='baz')
         t = DummyNode('t')
         s = DummyNode('s')
 
         lst = env.subst_list('test $BAR', target=t, source=s)
         assert lst == [['test', 'baz']], lst
 
-        # Test not calling callables in the Environment
+        # Test not calling callables in the SubstitutionEnvironment
         if 0:
             # This will take some serious surgery to subst() and
             # subst_list(), so just leave these tests out until we can
@@ -411,7 +534,7 @@ class EnvironmentTestCase(unittest.TestCase):
             def bar(arg):
                 pass
 
-            env = Environment(BAR=bar, FOO='$BAR')
+            env = SubstitutionEnvironment(BAR=bar, FOO='$BAR')
 
             subst = env.subst_list('$BAR', call=None)
             assert subst is bar, subst
@@ -428,10 +551,22 @@ class EnvironmentTestCase(unittest.TestCase):
             def get(self):
                 return self.val + '-proxy'
 
+        class MyNode:
+            def __init__(self, val):
+                self.val = val
+            def get_subst_proxy(self):
+                return self
+            def __str__(self):
+                return self.val
+
         class MyObj:
-            pass
+            def get(self):
+                return self
 
-        env = Environment(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
@@ -439,11 +574,373 @@ class EnvironmentTestCase(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 list(map(str, r)) == ['foo', 'ttt', 'bar'], r
+
+        r = env.subst_path(['$FOO', '$SOURCE', '$BAR'], source=MyNode('sss'))
+        assert list(map(str, r)) == ['foo', 'sss', 'bar'], r
+
         n = MyObj()
 
         r = env.subst_path(['$PROXY', MyProxy('my2'), n])
         assert r == ['my1-proxy', 'my2-proxy', n], r
 
+        class StringableObj:
+            def __init__(self, s):
+                self.s = s
+            def __str__(self):
+                return self.s
+
+        env = SubstitutionEnvironment(FOO=StringableObj("foo"),
+                          BAR=StringableObj("bar"))
+
+        r = env.subst_path([ "${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" ], r
+
+        r = env.subst_path([ "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"""
+        env = SubstitutionEnvironment(AAA = 'a', BBB = 'b')
+        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)
+        assert env['ONE'] == 1, env['ONE']
+        assert env['TWO'] == 2, env['TWO']
+        assert env['THREE'] == 3, env['THREE']
+        assert env['FOUR'] == 4, env['FOUR']
+
+        env2 = env.Override({'TWO'   : '10',
+                             'THREE' :'x $THREE y',
+                             'FOUR'  : ['x', '$FOUR', 'y']})
+        assert env2['ONE'] == 1, env2['ONE']
+        assert env2['TWO'] == '10', env2['TWO']
+        assert env2['THREE'] == 'x 3 y', env2['THREE']
+        assert env2['FOUR'] == ['x', 4, 'y'], env2['FOUR']
+
+        assert env['ONE'] == 1, env['ONE']
+        assert env['TWO'] == 2, env['TWO']
+        assert env['THREE'] == 3, env['THREE']
+        assert env['FOUR'] == 4, env['FOUR']
+
+        env2.Replace(ONE = "won")
+        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']
+
+        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
+
+        Create two with identical arguments and check that
+        they compare the same.
+        """
+        env1 = self.TestEnvironment(XXX = 'x', YYY = 'y')
+        env2 = self.TestEnvironment(XXX = 'x', YYY = 'y')
+        assert env1 == env2, diff_env(env1, env2)
+
+        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 = self.TestEnvironment(aaa = 'AAA')
+
+        x = env.get('aaa')
+        assert x == 'AAA', x
+        x = env.get('aaa', 'XXX')
+        assert x == 'AAA', x
+        x = env.get('bbb')
+        assert x is None, x
+        x = env.get('bbb', 'XXX')
+        assert x == 'XXX', x
+
     def test_Builder_calls(self):
         """Test Builder calls through different environments
         """
@@ -456,54 +953,81 @@ class EnvironmentTestCase(unittest.TestCase):
         env.Replace(BUILDERS = { 'builder1' : b1,
                                  'builder2' : b2 })
         called_it = {}
-        env.builder1(target = 'out1')
-        assert called_it['target'] == 'out1', called_it
-        assert not called_it.has_key('source')
+        env.builder1('in1')
+        assert called_it['target'] is None, called_it
+        assert called_it['source'] == ['in1'], called_it
 
         called_it = {}
-        env.builder2(target = 'out2', xyzzy = 1)
-        assert called_it['target'] == 'out2', called_it
+        env.builder2(source = 'in2', xyzzy = 1)
+        assert called_it['target'] is None, called_it
+        assert called_it['source'] == ['in2'], called_it
         assert called_it['xyzzy'] == 1, called_it
-        assert not called_it.has_key('source')
 
         called_it = {}
         env.builder1(foo = 'bar')
         assert called_it['foo'] == 'bar', called_it
-        assert not called_it.has_key('target')
-        assert not called_it.has_key('source')
+        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
+
+        assert bw.env is e1
+        bw.env = e2
+        assert bw.env is e2
 
+        assert bw.builder is b1
+        bw.builder = b2
+        assert bw.builder is b2
 
-    def test_Builder_execs(self):
-       """Test Builder execution through different environments
+        self.assertRaises(AttributeError, getattr, bw, 'foobar')
+        bw.foobar = 42
+        assert bw.foobar is 42
 
-       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
+    # 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()
+        b1 = Builder()
+        b2 = Builder()
 
-       built_it = {}
+        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')
@@ -519,71 +1043,166 @@ class EnvironmentTestCase(unittest.TestCase):
         assert built_it['out1']
         assert built_it['out2']
 
+
+
     def test_Scanners(self):
-        """Test Scanner execution through different environments
+        """Test setting SCANNERS in various ways
 
         One environment is initialized with a single
         Scanner object, one with a list of a single Scanner
         object, and one with a list of two Scanner objects.
         """
-#        global scanned_it
-#
-#      s1 = Scanner(name = 'scanner1', skeys = [".c", ".cc"])
-#      s2 = Scanner(name = 'scanner2', skeys = [".m4"])
-#
-#      scanned_it = {}
-#      env1 = Environment(SCANNERS = s1)
+        global scanned_it
+
+        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 = self.TestEnvironment(SCANNERS = s1)
 #        env1.scanner1(filename = 'out1')
-#      assert scanned_it['out1']
+#        assert scanned_it['out1']
 #
-#      scanned_it = {}
-#      env2 = Environment(SCANNERS = [s1])
+#        scanned_it = {}
+#        env2 = self.TestEnvironment(SCANNERS = [s1])
 #        env1.scanner1(filename = 'out1')
-#      assert scanned_it['out1']
+#        assert scanned_it['out1']
 #
-#      scanned_it = {}
+#        scanned_it = {}
 #        env3 = Environment()
-#        env3.Replace(SCANNERS = [s1, s2])
+#        env3.Replace(SCANNERS = [s1])
 #        env3.scanner1(filename = 'out1')
 #        env3.scanner2(filename = 'out2')
 #        env3.scanner1(filename = 'out3')
-#      assert scanned_it['out1']
-#      assert scanned_it['out2']
-#      assert scanned_it['out3']
-#
-#      s = env3.get_scanner(".c")
-#      assert s == s1, s
-#      s = env3.get_scanner(skey=".m4")
-#      assert s == s2, s
-#      s = env3.get_scanner(".cxx")
-#      assert s == None, s
+#        assert scanned_it['out1']
+#        assert scanned_it['out2']
+#        assert scanned_it['out3']
+
+        suffixes = [".c", ".cc", ".cxx", ".m4", ".m5"]
+
+        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 = list(map(env.get_scanner, suffixes))
+        assert s == [s1, s1, None, None, None], s
+
+        env.Append(SCANNERS = [s2])
+        s = list(map(env.get_scanner, suffixes))
+        assert s == [s1, s1, None, s2, None], s
+
+        env.AppendUnique(SCANNERS = [s3])
+        s = list(map(env.get_scanner, suffixes))
+        assert s == [s1, s1, None, s2, s3], s
+
+        env = env.Clone(SCANNERS = [s2])
+        s = list(map(env.get_scanner, suffixes))
+        assert s == [None, None, None, s2, None], s
+
+        env['SCANNERS'] = [s1]
+        s = list(map(env.get_scanner, suffixes))
+        assert s == [s1, s1, None, None, None], s
+
+        env.PrependUnique(SCANNERS = [s2, s1])
+        s = list(map(env.get_scanner, suffixes))
+        assert s == [s1, s1, None, s2, None], s
+
+        env.Prepend(SCANNERS = [s3])
+        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)
 
@@ -606,128 +1225,91 @@ class EnvironmentTestCase(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."""
@@ -739,7 +1321,7 @@ class EnvironmentTestCase(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"
@@ -761,7 +1343,7 @@ class EnvironmentTestCase(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"
@@ -778,13 +1360,29 @@ class EnvironmentTestCase(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
         t4(env)
         assert env['TOOL4'] == 444, env
 
+        test = TestCmd.TestCmd(workdir = '')
+        test.write('faketool.py', """\
+def generate(env, **kw):
+    for k, v in kw.items():
+        env[k] = v
+
+def exists(env):
+    return 1
+""")
+
+        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']
+        assert env['c'] == 3, env['c']
+
     def test_Default_TOOLS(self):
         """Test overriding the default TOOLS variable"""
         def t5(env):
@@ -811,9 +1409,26 @@ class EnvironmentTestCase(unittest.TestCase):
         finally:
             SCons.Defaults.ConstructionEnvironment = save
 
+    def test_null_tools(self):
+        """Test specifying a tool of None is OK."""
+        def t1(env):
+            env['TOOL1'] = 111
+        def t2(env):
+            env['TOOL2'] = 222
+        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 = self.TestEnvironment(tools = [None], XYZ = 'xyz')
+        assert env['XYZ'] == 'xyz', env
+        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
@@ -826,9 +1441,17 @@ class EnvironmentTestCase(unittest.TestCase):
         x = s("${_concat(PRE, LIST, SUF, __env__)}")
         assert x == 'preasuf prebsuf', x
 
+    def test_gvars(self):
+        """Test the Environment gvars() method"""
+        env = self.TestEnvironment(XXX = 'x', YYY = 'y', ZZZ = 'z')
+        gvars = env.gvars()
+        assert gvars['XXX'] == 'x', gvars['XXX']
+        assert gvars['YYY'] == 'y', gvars['YYY']
+        assert gvars['ZZZ'] == 'z', gvars['ZZZ']
+
     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']
@@ -856,6 +1479,8 @@ class EnvironmentTestCase(unittest.TestCase):
         b2 = Environment()['BUILDERS']
         assert b1 == b2, diff_dict(b1, b2)
 
+        import UserDict
+        UD = UserDict.UserDict
         import UserList
         UL = UserList.UserList
 
@@ -887,6 +1512,18 @@ class EnvironmentTestCase(unittest.TestCase):
             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']),
@@ -937,14 +1574,21 @@ class EnvironmentTestCase(unittest.TestCase):
         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
 
@@ -970,121 +1614,190 @@ class EnvironmentTestCase(unittest.TestCase):
 
         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'])
+                          BBB3 = ['b3'],
+                          BBB4 = ['b4'],
+                          BBB5 = ['b5'],
+                          CCC1 = '',
+                          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'],
+                         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
-
-       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
-       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()
-       env2.Replace(YYY = 'yyy')
-       assert env1 != env2
-       assert env1 == env1copy
-
-       env3 = env1.Copy(XXX = 'x3', ZZZ = 'z3')
-       assert env3.Dictionary('XXX') == 'x3'
-       assert env3.Dictionary('YYY') == 'y'
-       assert env3.Dictionary('ZZZ') == 'z3'
-       assert env1 == env1copy
-
-        assert env1['__env__'] is env1, env1['__env__']
-        assert env2['__env__'] is env2, env2['__env__']
-        assert env3['__env__'] is env3, env3['__env__']
+        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).
+        Clone the original with some construction variable
+        updates and check that the original remains intact
+        and the copy has the updated values.
+        """
+        env1 = self.TestEnvironment(XXX = 'x', YYY = 'y')
+        env2 = env1.Clone()
+        env1copy = env1.Clone()
+        assert env1copy == env1copy
+        assert env2 == env2
+        env2.Replace(YYY = 'yyy')
+        assert env2 == env2
+        assert env1 != env2
+        assert env1 == env1copy
+
+        env3 = env1.Clone(XXX = 'x3', ZZZ = 'z3')
+        assert env3 == env3
+        assert env3.Dictionary('XXX') == 'x3'
+        assert env3.Dictionary('YYY') == 'y'
+        assert env3.Dictionary('ZZZ') == 'z3'
+        assert env1 == env1copy
 
         # Ensure that lists and dictionaries are
         # 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
@@ -1098,8 +1811,8 @@ class EnvironmentTestCase(unittest.TestCase):
 
         # 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
@@ -1108,14 +1821,68 @@ class EnvironmentTestCase(unittest.TestCase):
 
         # 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 = '')
@@ -1127,14 +1894,14 @@ class EnvironmentTestCase(unittest.TestCase):
             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
@@ -1148,7 +1915,7 @@ class EnvironmentTestCase(unittest.TestCase):
             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
@@ -1156,7 +1923,7 @@ class EnvironmentTestCase(unittest.TestCase):
             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
@@ -1167,37 +1934,37 @@ class EnvironmentTestCase(unittest.TestCase):
             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',
@@ -1209,73 +1976,157 @@ class EnvironmentTestCase(unittest.TestCase):
 
         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_Override(self):
-        "Test overriding construction variables"
-        env = Environment(ONE=1, TWO=2, THREE=3, FOUR=4)
-        assert env['ONE'] == 1, env['ONE']
-        assert env['TWO'] == 2, env['TWO']
-        assert env['THREE'] == 3, env['THREE']
-        assert env['FOUR'] == 4, env['FOUR']
-
-        env2 = env.Override({'TWO'   : '10',
-                             'THREE' :'x $THREE y',
-                             'FOUR'  : ['x', '$FOUR', 'y']})
-        assert env2['ONE'] == 1, env2['ONE']
-        assert env2['TWO'] == '10', env2['TWO']
-        assert env2['THREE'] == 'x 3 y', env2['THREE']
-        assert env2['FOUR'] == ['x', 4, 'y'], env2['FOUR']
-
-        assert env['ONE'] == 1, env['ONE']
-        assert env['TWO'] == 2, env['TWO']
-        assert env['THREE'] == 3, env['THREE']
-        assert env['FOUR'] == 4, env['FOUR']
-
-        env2.Replace(ONE = "won")
-        assert env2['ONE'] == "won", env2['ONE']
-        assert env['ONE'] == 1, env['ONE']
-
-        assert env['__env__'] is env, env['__env__']
-        assert env2['__env__'] is env2, env2['__env__']
-
     def test_ParseConfig(self):
         """Test the ParseConfig() method"""
-        env = Environment(COMMAND='command',
+        env = self.TestEnvironment(COMMAND='command',
+                          ASFLAGS='assembler',
+                          CCFLAGS=[''],
+                          CPPDEFINES=[],
+                          CPPFLAGS=[''],
                           CPPPATH='string',
+                          FRAMEWORKPATH=[],
+                          FRAMEWORKS=[],
                           LIBPATH=['list'],
                           LIBS='',
-                          CCFLAGS=[''])
-        save_command = []
-        orig_popen = os.popen
-        def my_popen(command, save_command=save_command):
-            save_command.append(command)
-            class fake_file:
-                def read(self):
-                    return "-I/usr/include/fum -Ibar -X\n" + \
-                           "-L/usr/fax -Lfoo -lxxx abc"
-            return fake_file()
+                          LINKFLAGS=[''],
+                          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)
+                return self.output
+
         try:
-            os.popen = my_popen
-            libs = env.ParseConfig("fake $COMMAND")
+            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 " + \
+                                 "-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 libs == ['abc'], libs
+            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'], env['LIBS']
-            assert env['CCFLAGS'] == ['', '-X'], env['CCFLAGS']
+            assert env['LIBS'] == ['xxx', 'yyy', env.File('abc')], env['LIBS']
+            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"""
+        test = TestCmd.TestCmd(workdir = '')
+
+        test.write('single', """
+#file: dependency
+
+f0: \
+   d1 \
+   d2 \
+   d3 \
+
+""")
+
+        test.write('multiple', """
+f1: foo
+f2 f3: bar
+f4: abc def
+#file: dependency
+f5: \
+   ghi \
+   jkl \
+   mno \
+""")
+
+        env = self.TestEnvironment(SINGLE = test.workpath('single'))
+
+        tlist = []
+        dlist = []
+        def my_depends(target, dependency, tlist=tlist, dlist=dlist):
+            tlist.extend(target)
+            dlist.extend(dependency)
+
+        env.Depends = my_depends
+
+        env.ParseDepends(test.workpath('does_not_exist'))
+
+        exc_caught = None
+        try:
+            env.ParseDepends(test.workpath('does_not_exist'), must_exist=1)
+        except IOError:
+            exc_caught = 1
+        assert exc_caught, "did not catch expected IOError"
+
+        del tlist[:]
+        del dlist[:]
+
+        env.ParseDepends('$SINGLE', only_one=1)
+        t = list(map(str, tlist))
+        d = list(map(str, dlist))
+        assert t == ['f0'], t
+        assert d == ['d1', 'd2', 'd3'], d
+
+        del tlist[:]
+        del dlist[:]
+
+        env.ParseDepends(test.workpath('multiple'))
+        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
+
+        exc_caught = None
+        try:
+            env.ParseDepends(test.workpath('multiple'), only_one=1)
+        except SCons.Errors.UserError:
+            exc_caught = 1
+        assert exc_caught, "did not catch expected UserError"
 
     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:
@@ -1300,6 +2151,8 @@ class EnvironmentTestCase(unittest.TestCase):
     def test_Prepend(self):
         """Test prepending to construction variables in an Environment
         """
+        import UserDict
+        UD = UserDict.UserDict
         import UserList
         UL = UserList.UserList
 
@@ -1331,6 +2184,18 @@ class EnvironmentTestCase(unittest.TestCase):
             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']),
@@ -1381,14 +2246,21 @@ class EnvironmentTestCase(unittest.TestCase):
         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
 
@@ -1404,85 +2276,142 @@ class EnvironmentTestCase(unittest.TestCase):
         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'])
+                          BBB3 = ['b3'],
+                          BBB4 = ['b4'],
+                          BBB5 = ['b5'],
+                          CCC1 = '',
+                          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'])
+                          BBB3 = ['b3', 'b', 'c', 'b3'],
+                          BBB4 = 'b4.new',
+                          BBB5 = ['b5.new'],
+                          CCC1 = 'c1',
+                          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',
@@ -1501,23 +2430,33 @@ class EnvironmentTestCase(unittest.TestCase):
                                              'PREFIX', 'SUFFIX',
                                              'LIBPREFIX', 'LIBSUFFIX')
 
+    def test_SetDefault(self):
+        """Test the SetDefault method"""
+        env = self.TestEnvironment(tools = [])
+        env.SetDefault(V1 = 1)
+        env.SetDefault(V1 = 2)
+        assert env['V1'] == 1
+        env['V2'] = 2
+        env.SetDefault(V2 = 1)
+        assert env['V2'] == 2
+
     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.
@@ -1527,6 +2466,29 @@ class EnvironmentTestCase(unittest.TestCase):
         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 = '')
@@ -1555,30 +2517,35 @@ class EnvironmentTestCase(unittest.TestCase):
                           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)
+        assert wi == test.workpath(sub4_xxx_exe), wi
+        wi = env.WhereIs('xxx.exe', pathdirs_1243, reject = sub3_xxx_exe)
         assert wi == test.workpath(sub4_xxx_exe), wi
 
-        path = string.join(pathdirs_1243, os.pathsep)
-        env = Environment(ENV = {'PATH' : path})
+        path = os.pathsep.join(pathdirs_1243)
+        env = self.TestEnvironment(ENV = {'PATH' : path})
         wi = env.WhereIs('xxx.exe')
         assert wi == test.workpath(sub4_xxx_exe), wi
         wi = env.WhereIs('xxx.exe', pathdirs_1234)
         assert wi == test.workpath(sub3_xxx_exe), wi
-        wi = env.WhereIs('xxx.exe', 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':
@@ -1589,13 +2556,13 @@ class EnvironmentTestCase(unittest.TestCase):
             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
 
 
 
@@ -1603,29 +2570,33 @@ class EnvironmentTestCase(unittest.TestCase):
         """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.CommandGeneratorAction, 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]
@@ -1636,7 +2607,7 @@ class EnvironmentTestCase(unittest.TestCase):
 
     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]
@@ -1647,128 +2618,149 @@ class EnvironmentTestCase(unittest.TestCase):
 
     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')
+        tgt = env.Alias('new_alias')[0]
         assert str(tgt) == 'new_alias', tgt
         assert tgt.sources == [], tgt.sources
+        assert not hasattr(tgt, 'builder'), tgt.builder
 
-        tgt = env.Alias('None_alias', None)
+        tgt = env.Alias('None_alias', None)[0]
         assert str(tgt) == 'None_alias', tgt
         assert tgt.sources == [], tgt.sources
 
-        tgt = env.Alias('empty_list', [])
+        tgt = env.Alias('empty_list', [])[0]
         assert str(tgt) == 'empty_list', tgt
         assert tgt.sources == [], tgt.sources
 
-        tgt = env.Alias('export_alias', [ 'asrc1', '$FOO' ])
+        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'])
+        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')
+        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 = list(map(str, tgt.sources))
+        assert s == ['s1', 's2'], s
+        tgt = env.Alias(tgt, 's3')[0]
+        s = list(map(str, tgt.sources))
+        assert s == ['s1', 's2', 's3'], s
+
+        tgt = env.Alias('act', None, "action1")[0]
+        s = str(tgt.builder.action)
+        assert s == "action1", s
+        tgt = env.Alias('act', None, "action2")[0]
+        s = str(tgt.builder.action)
+        assert s == "action1\naction2", s
+        tgt = env.Alias(tgt, None, "action3")[0]
+        s = str(tgt.builder.action)
+        assert s == "action1\naction2\naction3", s
 
     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
 
@@ -1776,84 +2768,93 @@ class EnvironmentTestCase(unittest.TestCase):
         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):
         """Test the Command() method."""
         env = Environment()
         t = env.Command(target='foo.out', source=['foo1.in', 'foo2.in'],
-                        action='buildfoo $target $source')
-        assert not t.builder is None
+                        action='buildfoo $target $source')[0]
+        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')
-        assert 'sub' in map(lambda x: x.path, t.sources)
+                        action='buildbar $target $source')[0]
+        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)
-        assert not t.builder is None
+                        action=testFunc)[0]
+        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')
-        assert not t.builder is None
+                        XYZ='magic word')[0]
+        assert t.builder is not None
         t.build()
         assert x[0] == 'magic word', x
 
+        t = env.Command(target='${X}.out', source='${X}.in',
+                        action = 'foo',
+                        X = 'xxx')[0]
+        assert str(t) == 'xxx.out', str(t)
+        assert 'xxx.in' in [x.path for x in t.sources]
+
+        env = self.TestEnvironment(source_scanner = 'should_not_find_this')
+        t = env.Command(target='file.out', source='file.in',
+                        action = 'foo',
+                        source_scanner = 'fake')[0]
+        assert t.builder.source_scanner == 'fake', t.builder.source_scanner
+
     def test_Configure(self):
         """Test the Configure() method"""
         # Configure() will write to a local temporary file.
         test = TestCmd.TestCmd(workdir = '')
         save = os.getcwd()
-        # Configure() will test, if we are reading a SConscript file
-        import SCons.Script.SConscript
-        SCons.Script.SConscript.sconscript_reading = 1
 
         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()
@@ -1862,12 +2863,13 @@ class EnvironmentTestCase(unittest.TestCase):
 
     def test_Depends(self):
         """Test the explicit Depends method."""
-        env = Environment(FOO = 'xxx', BAR='yyy')
+        env = self.TestEnvironment(FOO = 'xxx', BAR='yyy')
         env.Dir('dir1')
         env.Dir('dir2')
         env.File('xxx.py')
         env.File('yyy.py')
-        t = env.Depends(target='EnvironmentTest.py', dependency='Environment.py')
+        t = env.Depends(target='EnvironmentTest.py',
+                        dependency='Environment.py')[0]
         assert t.__class__.__name__ == 'Entry', t.__class__.__name__
         assert t.path == 'EnvironmentTest.py'
         assert len(t.depends) == 1
@@ -1875,7 +2877,7 @@ class EnvironmentTestCase(unittest.TestCase):
         assert d.__class__.__name__ == 'Entry', d.__class__.__name__
         assert d.path == 'Environment.py'
 
-        t = env.Depends(target='${FOO}.py', dependency='${BAR}.py')
+        t = env.Depends(target='${FOO}.py', dependency='${BAR}.py')[0]
         assert t.__class__.__name__ == 'File', t.__class__.__name__
         assert t.path == 'xxx.py'
         assert len(t.depends) == 1
@@ -1883,7 +2885,7 @@ class EnvironmentTestCase(unittest.TestCase):
         assert d.__class__.__name__ == 'File', d.__class__.__name__
         assert d.path == 'yyy.py'
 
-        t = env.Depends(target='dir1', dependency='dir2')
+        t = env.Depends(target='dir1', dependency='dir2')[0]
         assert t.__class__.__name__ == 'Dir', t.__class__.__name__
         assert t.path == 'dir1'
         assert len(t.depends) == 1
@@ -1897,7 +2899,7 @@ class EnvironmentTestCase(unittest.TestCase):
             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')
@@ -1909,9 +2911,45 @@ class EnvironmentTestCase(unittest.TestCase):
         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 = 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']
@@ -1932,13 +2970,37 @@ class EnvironmentTestCase(unittest.TestCase):
         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')
@@ -1950,18 +3012,32 @@ class EnvironmentTestCase(unittest.TestCase):
         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
 
         # XXX
 
+    def test_Flatten(self):
+        """Test the Flatten() method"""
+        env = Environment()
+        l = env.Flatten([1])
+        assert l == [1]
+        l = env.Flatten([1, [2, [3, [4]]]])
+        assert l == [1, 2, 3, 4], l
+
     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
@@ -1971,13 +3047,13 @@ class EnvironmentTestCase(unittest.TestCase):
 
     def test_Ignore(self):
         """Test the explicit Ignore method."""
-        env = Environment(FOO='yyy', BAR='zzz')
+        env = self.TestEnvironment(FOO='yyy', BAR='zzz')
         env.Dir('dir1')
         env.Dir('dir2')
         env.File('yyyzzz')
         env.File('zzzyyy')
 
-        t = env.Ignore(target='targ.py', dependency='dep.py')
+        t = env.Ignore(target='targ.py', dependency='dep.py')[0]
         assert t.__class__.__name__ == 'Entry', t.__class__.__name__
         assert t.path == 'targ.py'
         assert len(t.ignore) == 1
@@ -1985,7 +3061,7 @@ class EnvironmentTestCase(unittest.TestCase):
         assert i.__class__.__name__ == 'Entry', i.__class__.__name__
         assert i.path == 'dep.py'
 
-        t = env.Ignore(target='$FOO$BAR', dependency='$BAR$FOO')
+        t = env.Ignore(target='$FOO$BAR', dependency='$BAR$FOO')[0]
         assert t.__class__.__name__ == 'File', t.__class__.__name__
         assert t.path == 'yyyzzz'
         assert len(t.ignore) == 1
@@ -1993,7 +3069,7 @@ class EnvironmentTestCase(unittest.TestCase):
         assert i.__class__.__name__ == 'File', i.__class__.__name__
         assert i.path == 'zzzyyy'
 
-        t = env.Ignore(target='dir1', dependency='dir2')
+        t = env.Ignore(target='dir1', dependency='dir2')[0]
         assert t.__class__.__name__ == 'Dir', t.__class__.__name__
         assert t.path == 'dir1'
         assert len(t.ignore) == 1
@@ -2001,75 +3077,9 @@ class EnvironmentTestCase(unittest.TestCase):
         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')
-        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]
@@ -2077,7 +3087,7 @@ class EnvironmentTestCase(unittest.TestCase):
 
     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]
@@ -2088,7 +3098,7 @@ class EnvironmentTestCase(unittest.TestCase):
 
     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')
@@ -2118,7 +3128,7 @@ class EnvironmentTestCase(unittest.TestCase):
                 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')
@@ -2130,31 +3140,32 @@ class EnvironmentTestCase(unittest.TestCase):
         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"""
-        import SCons.Sig
+        import SCons.SConsign
 
         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 = []
@@ -2163,92 +3174,100 @@ class EnvironmentTestCase(unittest.TestCase):
                 fnames.append(name)
                 dbms.append(dbm_module)
 
-            save_Sig_SConsignFile = SCons.Sig.SConsignFile
-            SCons.Sig.SConsignFile = capture
+            save_SConsign_File = SCons.SConsign.File
+            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[-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.Sig.SConsignFile = save_Sig_SConsignFile
+            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')
 
-        foo = env.Object('foo.obj', 'foo.cpp')
-        bar = env.Object('bar.obj', 'bar.cpp')
-        s = env.SideEffect('mylib.pdb', ['foo.obj', 'bar.obj'])
+        foo = env.Object('foo.obj', 'foo.cpp')[0]
+        bar = env.Object('bar.obj', 'bar.cpp')[0]
+        s = env.SideEffect('mylib.pdb', ['foo.obj', 'bar.obj'])[0]
         assert s.__class__.__name__ == 'Entry', s.__class__.__name__
         assert s.path == 'mylib.pdb'
         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')
-        bbb = env.Object('bbb.obj', 'bbb.cpp')
-        s = env.SideEffect('my${LIB}.pdb', ['${FOO}.obj', '${BAR}.obj'])
+        fff = env.Object('fff.obj', 'fff.cpp')[0]
+        bbb = env.Object('bbb.obj', 'bbb.cpp')[0]
+        s = env.SideEffect('my${LIB}.pdb', ['${FOO}.obj', '${BAR}.obj'])[0]
         assert s.__class__.__name__ == 'File', s.__class__.__name__
         assert s.path == 'mylll.pdb'
         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')
-        ccc = env.Object('ccc.obj', 'ccc.cpp')
-        s = env.SideEffect('mymmm.pdb', ['ggg.obj', 'ccc.obj'])
+        ggg = env.Object('ggg.obj', 'ggg.cpp')[0]
+        ccc = env.Object('ccc.obj', 'ccc.cpp')[0]
+        s = env.SideEffect('mymmm.pdb', ['ggg.obj', 'ccc.obj'])[0]
         assert s.__class__.__name__ == 'Dir', s.__class__.__name__
         assert s.path == 'mymmm.pdb'
         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')
-        e = env.SourceCode('foo', None)
+        env = self.TestEnvironment(FOO='mmm', BAR='nnn')
+        e = env.SourceCode('foo', None)[0]
         assert e.path == 'foo'
         s = e.src_builder()
         assert s is None, s
 
         b = Builder()
-        e = env.SourceCode(e, b)
+        e = env.SourceCode(e, b)[0]
         assert e.path == 'foo'
         s = e.src_builder()
         assert s is b, s
 
-        e = env.SourceCode('$BAR$FOO', None)
+        e = env.SourceCode('$BAR$FOO', None)[0]
         assert e.path == 'nnnmmm'
         s = e.src_builder()
         assert s is None, s
 
     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:
@@ -2256,23 +3275,35 @@ class EnvironmentTestCase(unittest.TestCase):
         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")
@@ -2288,7 +3319,9 @@ class EnvironmentTestCase(unittest.TestCase):
 
     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:
@@ -2299,16 +3332,41 @@ class EnvironmentTestCase(unittest.TestCase):
         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
@@ -2325,6 +3383,9 @@ class EnvironmentTestCase(unittest.TestCase):
         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):
@@ -2343,13 +3404,440 @@ class EnvironmentTestCase(unittest.TestCase):
         f = env.xxx('$FOO')
         assert f == 'foo', f
 
+    def test_bad_keywords(self):
+        """Test trying to use reserved keywords in an Environment"""
+        added = []
+
+        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 self.reserved_variables:
+            assert x not in env, env[x]
+        for x in added:
+            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 self.reserved_variables:
+            assert x not in env, env[x]
+        for x in added:
+            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 self.reserved_variables:
+            assert x not in env, env[x]
+        for x in added:
+            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 self.reserved_variables:
+            assert x not in env, env[x]
+        for x in added:
+            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 self.reserved_variables:
+            assert x not in env, env[x]
+        for x in added:
+            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 self.reserved_variables:
+            assert x not in env, env[x]
+        for x in added:
+            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 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 self.reserved_variables:
+            assert x not in over, over[x]
+        for x in added + ['OVERRIDE']:
+            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,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, env2, env3 = self.envs
+        assert env['XXX'] == 'x', env['XXX']
+        assert env2['XXX'] == 'x2', env2['XXX']
+        assert env3['XXX'] == 'x3', env3['XXX']
+        assert env['YYY'] == 'y', env['YYY']
+        assert env2['YYY'] == 'y', env2['YYY']
+        assert env3['YYY'] == 'y3', env3['YYY']
+
+    def test___delitem__(self):
+        """Test deleting variables from an OverrideEnvironment"""
+        env, env2, env3 = self.envs
 
+        del env3['XXX']
+        assert 'XXX' not in env, "env has XXX?"
+        assert 'XXX' not in env2, "env2 has XXX?"
+        assert 'XXX' not in env3, "env3 has XXX?"
 
-class NoSubstitutionProxyTestCase(unittest.TestCase):
+        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, 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') 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, 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, 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, env2, env3 = self.envs
+        gvars = env.gvars()
+        assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars
+        gvars = env2.gvars()
+        assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars
+        gvars = env3.gvars()
+        assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars
+
+    def test_lvars(self):
+        """Test the OverrideEnvironment lvars() method"""
+        env, env2, env3 = self.envs
+        lvars = env.lvars()
+        assert lvars == {}, lvars
+        lvars = env2.lvars()
+        assert lvars == {'XXX' : 'x2'}, lvars
+        lvars = env3.lvars()
+        assert lvars == {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'}, lvars
+
+    def test_Replace(self):
+        """Test the OverrideEnvironment Replace() method"""
+        env, env2, env3 = self.envs
+        assert env['XXX'] == 'x', env['XXX']
+        assert env2['XXX'] == 'x2', env2['XXX']
+        assert env3['XXX'] == 'x3', env3['XXX']
+        assert env['YYY'] == 'y', env['YYY']
+        assert env2['YYY'] == 'y', env2['YYY']
+        assert env3['YYY'] == 'y3', env3['YYY']
+
+        env.Replace(YYY = 'y4')
+
+        assert env['XXX'] == 'x', env['XXX']
+        assert env2['XXX'] == 'x2', env2['XXX']
+        assert env3['XXX'] == 'x3', env3['XXX']
+        assert env['YYY'] == 'y4', env['YYY']
+        assert env2['YYY'] == 'y4', env2['YYY']
+        assert env3['YYY'] == 'y3', env3['YYY']
+
+    # Tests a number of Base methods through an OverrideEnvironment to
+    # make sure they handle overridden constructionv variables properly.
+    #
+    # The following Base methods also call self.subst(), and so could
+    # theoretically be subject to problems with evaluating overridden
+    # variables, but they're never really called that way in the rest
+    # of our code, so we won't worry about them (at least for now):
+    #
+    # ParseConfig()
+    # ParseDepends()
+    # Platform()
+    # Tool()
+    #
+    # Action()
+    # Alias()
+    # Builder()
+    # CacheDir()
+    # Configure()
+    # Environment()
+    # FindFile()
+    # Scanner()
+    # SourceSignatures()
+    # TargetSignatures()
+
+    # It's unlikely Clone() will ever be called this way, so let the
+    # other methods test that handling overridden values works.
+    #def test_Clone(self):
+    #    """Test the OverrideEnvironment Clone() method"""
+    #    pass
+
+    def test_FindIxes(self):
+        """Test the OverrideEnvironment FindIxes() method"""
+        env, env2, env3 = self.envs
+        x = env.FindIxes(['xaaay'], 'XXX', 'YYY')
+        assert x == 'xaaay', x
+        x = env2.FindIxes(['x2aaay'], 'XXX', 'YYY')
+        assert x == 'x2aaay', x
+        x = env3.FindIxes(['x3aaay3'], 'XXX', 'YYY')
+        assert x == 'x3aaay3', x
+
+    def test_ReplaceIxes(self):
+        """Test the OverrideEnvironment ReplaceIxes() method"""
+        env, env2, env3 = self.envs
+        x = env.ReplaceIxes('xaaay', 'XXX', 'YYY', 'YYY', 'XXX')
+        assert x == 'yaaax', x
+        x = env2.ReplaceIxes('x2aaay', 'XXX', 'YYY', 'YYY', 'XXX')
+        assert x == 'yaaax2', x
+        x = env3.ReplaceIxes('x3aaay3', 'XXX', 'YYY', 'YYY', 'XXX')
+        assert x == 'y3aaax3', x
+
+    # It's unlikely WhereIs() will ever be called this way, so let the
+    # other methods test that handling overridden values works.
+    #def test_WhereIs(self):
+    #    """Test the OverrideEnvironment WhereIs() method"""
+    #    pass
+
+    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']
 
@@ -2377,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']
 
@@ -2391,13 +3879,13 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
         assert x == '$XXX', x
 
         x = proxy.subst('$YYY', raw=7, target=None, source=None,
-                        dict=None, conv=None,
+                        conv=None,
                         extra_meaningless_keyword_argument=None)
         assert x == '$YYY', x
 
     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']
 
@@ -2412,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']
 
@@ -2425,13 +3913,12 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
         x = proxy.subst_list('$XXX')
         assert x == [[]], x
 
-        x = proxy.subst_list('$YYY', raw=0, target=None, source=None,
-                             dict=None, conv=None)
+        x = proxy.subst_list('$YYY', raw=0, target=None, source=None, conv=None)
         assert x == [[]], x
 
     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']
 
@@ -2441,19 +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 = [ EnvironmentTestCase,
-                 NoSubstitutionProxyTestCase ]
+    tclasses = [ SubstitutionTestCase,
+                 BaseTestCase,
+                 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: