http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / Node / FSTests.py
index 565384f7e2727019847ae037767de48f7c9af319..e68b389ab23658a663ecf56a080c3ed660e3c612 100644 (file)
@@ -25,7 +25,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os
 import os.path
-import string
 import sys
 import time
 import unittest
@@ -35,17 +34,11 @@ import stat
 
 import SCons.Errors
 import SCons.Node.FS
+import SCons.Util
 import SCons.Warnings
 
 built_it = None
 
-# This will be built-in in 2.3.  For now fake it.
-try :
-    True , False
-except NameError :
-    True = 1 ; False = 0
-
-
 scanner_count = 0
 
 class Scanner:
@@ -80,15 +73,22 @@ class Environment:
         pass
 
 class Action:
-    def __call__(self, targets, sources, env, errfunc, **kw):
+    def __call__(self, targets, sources, env, **kw):
         global built_it
         if kw.get('execute', 1):
             built_it = 1
         return 0
     def show(self, string):
         pass
+    def get_contents(self, target, source, env):
+        return ""
+    def genstring(self, target, source, env):
+        return ""
     def strfunction(self, targets, sources, env):
         return ""
+    def get_implicit_deps(self, target, source, env):
+        return []
+
 class Builder:
     def __init__(self, factory, action=Action()):
         self.factory = factory
@@ -115,14 +115,14 @@ class _tempdirTestCase(unittest.TestCase):
     def tearDown(self):
         os.chdir(self.save_cwd)
 
-class BuildDirTestCase(unittest.TestCase):
+class VariantDirTestCase(unittest.TestCase):
     def runTest(self):
-        """Test build dir functionality"""
+        """Test variant dir functionality"""
         test=TestCmd(workdir='')
 
         fs = SCons.Node.FS.FS()
         f1 = fs.File('build/test1')
-        fs.BuildDir('build', 'src')
+        fs.VariantDir('build', 'src')
         f2 = fs.File('build/test2')
         d1 = fs.Dir('build')
         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
@@ -131,7 +131,7 @@ class BuildDirTestCase(unittest.TestCase):
 
         fs = SCons.Node.FS.FS()
         f1 = fs.File('build/test1')
-        fs.BuildDir('build', '.')
+        fs.VariantDir('build', '.')
         f2 = fs.File('build/test2')
         d1 = fs.Dir('build')
         assert f1.srcnode().path == 'test1', f1.srcnode().path
@@ -139,16 +139,16 @@ class BuildDirTestCase(unittest.TestCase):
         assert d1.srcnode().path == '.', d1.srcnode().path
 
         fs = SCons.Node.FS.FS()
-        fs.BuildDir('build/var1', 'src')
-        fs.BuildDir('build/var2', 'src')
+        fs.VariantDir('build/var1', 'src')
+        fs.VariantDir('build/var2', 'src')
         f1 = fs.File('build/var1/test1')
         f2 = fs.File('build/var2/test1')
         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
         assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path
 
         fs = SCons.Node.FS.FS()
-        fs.BuildDir('../var1', 'src')
-        fs.BuildDir('../var2', 'src')
+        fs.VariantDir('../var1', 'src')
+        fs.VariantDir('../var2', 'src')
         f1 = fs.File('../var1/test1')
         f2 = fs.File('../var2/test1')
         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
@@ -172,11 +172,11 @@ class BuildDirTestCase(unittest.TestCase):
         # A source file in the repository
         test.write([ 'rep1', 'src', 'test2.in' ], 'test2.in')
 
-        # Some source files in the build directory
+        # Some source files in the variant directory
         test.write([ 'work', 'build', 'var2', 'test.in' ], 'test.old')
         test.write([ 'work', 'build', 'var2', 'test2.in' ], 'test2.old')
 
-        # An old derived file in the build directories
+        # An old derived file in the variant directories
         test.write([ 'work', 'build', 'var1', 'test.out' ], 'test.old')
         test.write([ 'work', 'build', 'var2', 'test.out' ], 'test.old')
 
@@ -191,8 +191,8 @@ class BuildDirTestCase(unittest.TestCase):
         os.chdir(test.workpath('work'))
 
         fs = SCons.Node.FS.FS(test.workpath('work'))
-        fs.BuildDir('build/var1', 'src', duplicate=0)
-        fs.BuildDir('build/var2', 'src')
+        fs.VariantDir('build/var1', 'src', duplicate=0)
+        fs.VariantDir('build/var2', 'src')
         f1 = fs.File('build/var1/test.in')
         f1out = fs.File('build/var1/test.out')
         f1out.builder = 1
@@ -280,8 +280,9 @@ class BuildDirTestCase(unittest.TestCase):
 
         assert not f7.exists()
         assert f7.rexists()
-        assert f7.rfile().path == os.path.normpath(test.workpath('rep1/build/var1/test2.out')),\
-               f7.rfile().path
+        r = f7.rfile().path
+        expect = os.path.normpath(test.workpath('rep1/build/var1/test2.out'))
+        assert r == expect, (repr(r), repr(expect))
 
         assert not f8.exists()
         assert f8.rexists()
@@ -295,7 +296,10 @@ class BuildDirTestCase(unittest.TestCase):
         class MkdirAction(Action):
             def __init__(self, dir_made):
                 self.dir_made = dir_made
-            def __call__(self, target, source, env, errfunc):
+            def __call__(self, target, source, env, executor=None):
+                if executor:
+                    target = executor.get_all_targets()
+                    source = executor.get_all_sources()
                 self.dir_made.extend(target)
 
         save_Link = SCons.Node.FS.Link
@@ -329,7 +333,7 @@ class BuildDirTestCase(unittest.TestCase):
 
         f11 = fs.File('src/file11')
         t, m = f11.alter_targets()
-        bdt = map(lambda n: n.path, t)
+        bdt = [n.path for n in t]
         var1_file11 = os.path.normpath('build/var1/file11')
         var2_file11 = os.path.normpath('build/var2/file11')
         assert bdt == [var1_file11, var2_file11], bdt
@@ -337,22 +341,22 @@ class BuildDirTestCase(unittest.TestCase):
         f12 = fs.File('src/file12')
         f12.builder = 1
         bdt, m = f12.alter_targets()
-        assert bdt == [], map(lambda n: n.path, bdt)
+        assert bdt == [], [n.path for n in bdt]
 
         d13 = fs.Dir('src/new_dir')
         t, m = d13.alter_targets()
-        bdt = map(lambda n: n.path, t)
+        bdt = [n.path for n in t]
         var1_new_dir = os.path.normpath('build/var1/new_dir')
         var2_new_dir = os.path.normpath('build/var2/new_dir')
         assert bdt == [var1_new_dir, var2_new_dir], bdt
 
         # Test that an IOError trying to Link a src file
-        # into a BuildDir ends up throwing a StopError.
+        # into a VariantDir ends up throwing a StopError.
         fIO = fs.File("build/var2/IOError")
 
         save_Link = SCons.Node.FS.Link
         def Link_IOError(target, source, env):
-            raise IOError, "Link_IOError"
+            raise IOError, (17, "Link_IOError")
         SCons.Node.FS.Link = SCons.Action.Action(Link_IOError, None)
 
         test.write(['work', 'src', 'IOError'], "work/src/IOError\n")
@@ -383,13 +387,13 @@ class BuildDirTestCase(unittest.TestCase):
         # This used to generate a UserError when we forbid the source
         # directory from being outside the top-level SConstruct dir.
         fs = SCons.Node.FS.FS()
-        fs.BuildDir('build', '/test/foo')
+        fs.VariantDir('build', '/test/foo')
 
         exc_caught = 0
         try:
             try:
                 fs = SCons.Node.FS.FS()
-                fs.BuildDir('build', 'build/src')
+                fs.VariantDir('build', 'build/src')
             except SCons.Errors.UserError:
                 exc_caught = 1
             assert exc_caught, "Should have caught a UserError."
@@ -398,25 +402,25 @@ class BuildDirTestCase(unittest.TestCase):
             test.unlink( "build/foo" )
 
         fs = SCons.Node.FS.FS()
-        fs.BuildDir('build', 'src1')
+        fs.VariantDir('build', 'src1')
 
-        # Calling the same BuildDir twice should work fine.
-        fs.BuildDir('build', 'src1')
+        # Calling the same VariantDir twice should work fine.
+        fs.VariantDir('build', 'src1')
 
-        # Trying to move a build dir to a second source dir
+        # Trying to move a variant dir to a second source dir
         # should blow up
         try:
-            fs.BuildDir('build', 'src2')
+            fs.VariantDir('build', 'src2')
         except SCons.Errors.UserError:
             pass
         else:
             assert 0, "Should have caught a UserError."
 
         # Test against a former bug.  Make sure we can get a repository
-        # path for the build directory itself!
+        # path for the variant directory itself!
         fs=SCons.Node.FS.FS(test.workpath('work'))
         test.subdir('work')
-        fs.BuildDir('build/var3', 'src', duplicate=0)
+        fs.VariantDir('build/var3', 'src', duplicate=0)
         d1 = fs.Dir('build/var3')
         r = d1.rdir()
         assert r == d1, "%s != %s" % (r, d1)
@@ -425,57 +429,39 @@ class BuildDirTestCase(unittest.TestCase):
         class LinkSimulator :
             """A class to intercept os.[sym]link() calls and track them."""
 
-            def __init__( self, duplicate ) :
+            def __init__( self, duplicate, link, symlink, copy ) :
                 self.duplicate = duplicate
-                self._reset()
-
-            def _reset( self ) :
-                """Reset the simulator if necessary"""
-                if not self._need_reset() : return # skip if not needed now
-                self.links_to_be_called = self.duplicate
-
-            def _need_reset( self ) :
-                """
-                Determines whether or not the simulator needs to be reset.
-                A reset is necessary if the object is first being initialized,
-                or if all three methods have been tried already.
-                """
-                return (
-                        ( not hasattr( self , "links_to_be_called" ) )
-                        or
-                        (self.links_to_be_called == "")
-                       )
+                self.have = {}
+                self.have['hard'] = link
+                self.have['soft'] = symlink
+                self.have['copy'] = copy
+
+                self.links_to_be_called = []
+                for link in self.duplicate.split('-'):
+                    if self.have[link]:
+                        self.links_to_be_called.append(link)
 
             def link_fail( self , src , dest ) :
-                self._reset()
-                l = string.split(self.links_to_be_called, "-")
-                next_link = l[0]
-                assert  next_link == "hard", \
+                next_link = self.links_to_be_called.pop(0)
+                assert next_link == "hard", \
                        "Wrong link order: expected %s to be called "\
                        "instead of hard" % next_link
-                self.links_to_be_called = string.join(l[1:], '-')
                 raise OSError( "Simulating hard link creation error." )
 
             def symlink_fail( self , src , dest ) :
-                self._reset()
-                l = string.split(self.links_to_be_called, "-")
-                next_link = l[0]
-                assert  next_link == "soft", \
+                next_link = self.links_to_be_called.pop(0)
+                assert next_link == "soft", \
                        "Wrong link order: expected %s to be called "\
                        "instead of soft" % next_link
-                self.links_to_be_called = string.join(l[1:], '-')
                 raise OSError( "Simulating symlink creation error." )
 
             def copy( self , src , dest ) :
-                self._reset()
-                l = string.split(self.links_to_be_called, "-")
-                next_link = l[0]
-                assert  next_link == "copy", \
+                next_link = self.links_to_be_called.pop(0)
+                assert next_link == "copy", \
                        "Wrong link order: expected %s to be called "\
                        "instead of copy" % next_link
-                self.links_to_be_called = string.join(l[1:], '-')
                 # copy succeeds, but use the real copy
-                self._real_copy(src, dest)
+                self.have['copy'](src, dest)
         # end class LinkSimulator
 
         try:
@@ -485,32 +471,31 @@ class BuildDirTestCase(unittest.TestCase):
             pass
 
         for duplicate in SCons.Node.FS.Valid_Duplicates:
-            simulator = LinkSimulator(duplicate)
-
             # save the real functions for later restoration
-            real_link = None
-            real_symlink = None
             try:
                 real_link = os.link
             except AttributeError:
-                pass
+                real_link = None
             try:
                 real_symlink = os.symlink
             except AttributeError:
-                pass
+                real_symlink = None
             real_copy = shutil.copy2
-            simulator._real_copy = real_copy # the simulator needs the real one
+
+            simulator = LinkSimulator(duplicate, real_link, real_symlink, real_copy)
 
             # override the real functions with our simulation
             os.link = simulator.link_fail
             os.symlink = simulator.symlink_fail
             shutil.copy2 = simulator.copy
-            SCons.Node.FS.set_duplicate(duplicate)
-
-            src_foo = test.workpath('src', 'foo')
-            build_foo = test.workpath('build', 'foo')
 
             try:
+
+                SCons.Node.FS.set_duplicate(duplicate)
+
+                src_foo = test.workpath('src', 'foo')
+                build_foo = test.workpath('build', 'foo')
+
                 test.write(src_foo, 'src/foo\n')
                 os.chmod(src_foo, stat.S_IRUSR)
                 try:
@@ -534,10 +519,10 @@ class BuildDirTestCase(unittest.TestCase):
                     delattr(os, 'symlink')
                 shutil.copy2 = real_copy
 
-        # Test BuildDir "reflection," where a same-named subdirectory
-        # exists underneath a build_dir.
+        # Test VariantDir "reflection," where a same-named subdirectory
+        # exists underneath a variant_dir.
         fs = SCons.Node.FS.FS()
-        fs.BuildDir('work/src/b1/b2', 'work/src')
+        fs.VariantDir('work/src/b1/b2', 'work/src')
 
         dir_list = [
                 'work/src',
@@ -545,6 +530,8 @@ class BuildDirTestCase(unittest.TestCase):
                 'work/src/b1/b2',
                 'work/src/b1/b2/b1',
                 'work/src/b1/b2/b1/b2',
+                'work/src/b1/b2/b1/b2/b1',
+                'work/src/b1/b2/b1/b2/b1/b2',
         ]
 
         srcnode_map = {
@@ -554,6 +541,10 @@ class BuildDirTestCase(unittest.TestCase):
                 'work/src/b1/b2/b1/f' : 'work/src/b1/f',
                 'work/src/b1/b2/b1/b2' : 'work/src/b1/b2',
                 'work/src/b1/b2/b1/b2/f' : 'work/src/b1/b2/f',
+                'work/src/b1/b2/b1/b2/b1' : 'work/src/b1/b2/b1',
+                'work/src/b1/b2/b1/b2/b1/f' : 'work/src/b1/b2/b1/f',
+                'work/src/b1/b2/b1/b2/b1/b2' : 'work/src/b1/b2/b1/b2',
+                'work/src/b1/b2/b1/b2/b1/b2/f' : 'work/src/b1/b2/b1/b2/f',
         }
 
         alter_map = {
@@ -612,7 +603,7 @@ class BaseTestCase(_tempdirTestCase):
 
         e1 = fs.Entry('e1')
         s = e1.stat()
-        assert not s is None, s
+        assert s is not None, s
 
         e2 = fs.Entry('e2')
         s = e2.stat()
@@ -698,64 +689,129 @@ class BaseTestCase(_tempdirTestCase):
             nonexistent = fs.Entry('nonexistent')
             assert not nonexistent.islink()
 
-class NodeInfoTestCase(_tempdirTestCase):
+class DirNodeInfoTestCase(_tempdirTestCase):
     def test___init__(self):
-        """Test NodeInfo initialization"""
-        ni = SCons.Node.FS.NodeInfo()
-        assert not hasattr(ni, 'bsig')
+        """Test DirNodeInfo initialization"""
+        ddd = self.fs.Dir('ddd')
+        ni = SCons.Node.FS.DirNodeInfo(ddd)
 
-    def test___cmp__(self):
-        """Test comparing NodeInfo objects"""
-        ni1 = SCons.Node.FS.NodeInfo()
-        ni2 = SCons.Node.FS.NodeInfo()
+class DirBuildInfoTestCase(_tempdirTestCase):
+    def test___init__(self):
+        """Test DirBuildInfo initialization"""
+        ddd = self.fs.Dir('ddd')
+        bi = SCons.Node.FS.DirBuildInfo(ddd)
 
-        msg = "cmp(%s, %s) returned %s, not %s"
+class FileNodeInfoTestCase(_tempdirTestCase):
+    def test___init__(self):
+        """Test FileNodeInfo initialization"""
+        fff = self.fs.File('fff')
+        ni = SCons.Node.FS.FileNodeInfo(fff)
+        assert isinstance(ni, SCons.Node.FS.FileNodeInfo)
 
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1, ni2, c, 1)
+    def test_update(self):
+        """Test updating a File.NodeInfo with on-disk information"""
+        test = self.test
+        fff = self.fs.File('fff')
 
-        ni1.bsig = 777
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1.bsig, ni2, c, 1)
+        ni = SCons.Node.FS.FileNodeInfo(fff)
 
-        ni2.bsig = 666
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1.bsig, ni2.bsig, c, 1)
+        test.write('fff', "fff\n")
 
-        ni2.bsig = 777
-        c = cmp(ni1, ni2)
-        assert c == 0, msg % (ni1.bsig, ni2.bsig, c, 0)
+        st = os.stat('fff')
 
-        ni2.bsig = 888
-        c = cmp(ni1, ni2)
-        assert c == -1, msg % (ni1.bsig, ni2.bsig, c, -1)
+        ni.update(fff)
 
-    def test_update(self):
-        """Test updating a NodeInfo with on-disk information"""
-        test = self.test
-        test.write('fff', "fff\n")
-        fff = self.fs.File('fff')
+        assert hasattr(ni, 'timestamp')
+        assert hasattr(ni, 'size')
 
-        ni = SCons.Node.FS.NodeInfo()
-        assert not hasattr(ni, 'timestamp')
-        assert not hasattr(ni, 'size')
+        ni.timestamp = 0
+        ni.size = 0
 
         ni.update(fff)
-        assert ni.timestamp == os.path.getmtime('fff'), ni.timestamp
-        assert ni.size == os.path.getsize('fff'), ni.size
 
-class BuildInfoTestCase(_tempdirTestCase):
+        mtime = st[stat.ST_MTIME]
+        assert ni.timestamp == mtime, (ni.timestamp, mtime)
+        size = st[stat.ST_SIZE]
+        assert ni.size == size, (ni.size, size)
+
+        import time
+        time.sleep(2)
+
+        test.write('fff', "fff longer size, different time stamp\n")
+
+        st = os.stat('fff')
+
+        mtime = st[stat.ST_MTIME]
+        assert ni.timestamp != mtime, (ni.timestamp, mtime)
+        size = st[stat.ST_SIZE]
+        assert ni.size != size, (ni.size, size)
+
+        #fff.clear()
+        #ni.update(fff)
+
+        #st = os.stat('fff')
+
+        #mtime = st[stat.ST_MTIME]
+        #assert ni.timestamp == mtime, (ni.timestamp, mtime)
+        #size = st[stat.ST_SIZE]
+        #assert ni.size == size, (ni.size, size)
+
+class FileBuildInfoTestCase(_tempdirTestCase):
     def test___init__(self):
-        """Test BuildInfo initialization"""
+        """Test File.BuildInfo initialization"""
         fff = self.fs.File('fff')
-        bi = SCons.Node.FS.BuildInfo(fff)
-        assert bi.node is fff, bi.node
+        bi = SCons.Node.FS.FileBuildInfo(fff)
+        assert bi, bi
 
     def test_convert_to_sconsign(self):
         """Test converting to .sconsign file format"""
+        fff = self.fs.File('fff')
+        bi = SCons.Node.FS.FileBuildInfo(fff)
+        assert hasattr(bi, 'convert_to_sconsign')
 
     def test_convert_from_sconsign(self):
         """Test converting from .sconsign file format"""
+        fff = self.fs.File('fff')
+        bi = SCons.Node.FS.FileBuildInfo(fff)
+        assert hasattr(bi, 'convert_from_sconsign')
+
+    def test_prepare_dependencies(self):
+        """Test that we have a prepare_dependencies() method"""
+        fff = self.fs.File('fff')
+        bi = SCons.Node.FS.FileBuildInfo(fff)
+        bi.prepare_dependencies()
+
+    def test_format(self):
+        """Test the format() method"""
+        f1 = self.fs.File('f1')
+        bi1 = SCons.Node.FS.FileBuildInfo(f1)
+
+        s1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n1'))
+        s1sig.csig = 1
+        d1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n2'))
+        d1sig.timestamp = 2
+        i1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n3'))
+        i1sig.size = 3
+
+        bi1.bsources = [self.fs.File('s1')]
+        bi1.bdepends = [self.fs.File('d1')]
+        bi1.bimplicit = [self.fs.File('i1')]
+        bi1.bsourcesigs = [s1sig]
+        bi1.bdependsigs = [d1sig]
+        bi1.bimplicitsigs = [i1sig]
+        bi1.bact = 'action'
+        bi1.bactsig = 'actionsig'
+
+        expect_lines = [
+            's1: 1 None None',
+            'd1: None 2 None',
+            'i1: None None 3',
+            'actionsig [action]',
+        ]
+
+        expect = '\n'.join(expect_lines)
+        format = bi1.format()
+        assert format == expect, (repr(expect), repr(format))
 
 class FSTestCase(_tempdirTestCase):
     def test_runTest(self):
@@ -834,7 +890,7 @@ class FSTestCase(_tempdirTestCase):
         except TypeError:
             pass
         else:
-            assert 0
+            raise Exception, "did not catch expected TypeError"
 
         assert x1.Entry(x4) == x4
         try:
@@ -842,7 +898,7 @@ class FSTestCase(_tempdirTestCase):
         except TypeError:
             pass
         else:
-            assert 0
+            raise Exception, "did not catch expected TypeError"
 
         x6 = x1.File(x6)
         assert isinstance(x6, SCons.Node.FS.File)
@@ -854,41 +910,49 @@ class FSTestCase(_tempdirTestCase):
         if os.sep != '/':
             seps = seps + ['/']
 
+        drive, path = os.path.splitdrive(os.getcwd())
+
+        def _do_Dir_test(lpath, path_, abspath_, up_path_, sep, fileSys=fs, drive=drive):
+            dir = fileSys.Dir(lpath.replace('/', sep))
+
+            if os.sep != '/':
+                path_ = path_.replace('/', os.sep)
+                abspath_ = abspath_.replace('/', os.sep)
+                up_path_ = up_path_.replace('/', os.sep)
+
+            def strip_slash(p, drive=drive):
+                if p[-1] == os.sep and len(p) > 1:
+                    p = p[:-1]
+                if p[0] == os.sep:
+                    p = drive + p
+                return p
+            path = strip_slash(path_)
+            abspath = strip_slash(abspath_)
+            up_path = strip_slash(up_path_)
+            name = abspath.split(os.sep)[-1]
+
+            assert dir.name == name, \
+                   "dir.name %s != expected name %s" % \
+                   (dir.name, name)
+            assert dir.path == path, \
+                   "dir.path %s != expected path %s" % \
+                   (dir.path, path)
+            assert str(dir) == path, \
+                   "str(dir) %s != expected path %s" % \
+                   (str(dir), path)
+            assert dir.get_abspath() == abspath, \
+                   "dir.abspath %s != expected absolute path %s" % \
+                   (dir.get_abspath(), abspath)
+            assert dir.up().path == up_path, \
+                   "dir.up().path %s != expected parent path %s" % \
+                   (dir.up().path, up_path)
+
         for sep in seps:
 
-            def Dir_test(lpath, path_, abspath_, up_path_, fileSys=fs, s=sep):
-                dir = fileSys.Dir(string.replace(lpath, '/', s))
-
-                if os.sep != '/':
-                    path_ = string.replace(path_, '/', os.sep)
-                    abspath_ = string.replace(abspath_, '/', os.sep)
-                    up_path_ = string.replace(up_path_, '/', os.sep)
-
-                def strip_slash(p):
-                    if p[-1] == os.sep and len(p) > 1:
-                        p = p[:-1]
-                    return p
-                path = strip_slash(path_)
-                abspath = strip_slash(abspath_)
-                up_path = strip_slash(up_path_)
-                name = string.split(abspath, os.sep)[-1]
-
-                assert dir.name == name, \
-                       "dir.name %s != expected name %s" % \
-                       (dir.name, name)
-                assert dir.path == path, \
-                       "dir.path %s != expected path %s" % \
-                       (dir.path, path)
-                assert str(dir) == path, \
-                       "str(dir) %s != expected path %s" % \
-                       (str(dir), path)
-                assert dir.get_abspath() == abspath, \
-                       "dir.abspath %s != expected absolute path %s" % \
-                       (dir.get_abspath(), abspath)
-                assert dir.up().path == up_path, \
-                       "dir.up().path %s != expected parent path %s" % \
-                       (dir.up().path, up_path)
+            def Dir_test(lpath, path_, abspath_, up_path_, sep=sep, func=_do_Dir_test):
+                return func(lpath, path_, abspath_, up_path_, sep)
 
+            Dir_test('',            './',          sub_dir,           sub)
             Dir_test('foo',         'foo/',        sub_dir_foo,       './')
             Dir_test('foo/bar',     'foo/bar/',    sub_dir_foo_bar,   'foo/')
             Dir_test('/foo',        '/foo/',       '/foo/',           '/')
@@ -906,7 +970,7 @@ class FSTestCase(_tempdirTestCase):
             Dir_test('#',           './',          sub_dir,           sub)
 
             try:
-                f2 = fs.File(string.join(['f1', 'f2'], sep), directory = d1)
+                f2 = fs.File(sep.join(['f1', 'f2']), directory = d1)
             except TypeError, x:
                 assert str(x) == ("Tried to lookup File '%s' as a Dir." %
                                   d1_f1), x
@@ -914,7 +978,7 @@ class FSTestCase(_tempdirTestCase):
                 raise
 
             try:
-                dir = fs.Dir(string.join(['d1', 'f1'], sep))
+                dir = fs.Dir(sep.join(['d1', 'f1']))
             except TypeError, x:
                 assert str(x) == ("Tried to lookup File '%s' as a Dir." %
                                   d1_f1), x
@@ -929,35 +993,29 @@ class FSTestCase(_tempdirTestCase):
             except:
                 raise
 
-            # Test that just specifying the drive works to identify
-            # its root directory.
-            p = os.path.abspath(test.workpath('root_file'))
+        # Test that just specifying the drive works to identify
+        # its root directory.
+        p = os.path.abspath(test.workpath('root_file'))
+        drive, path = os.path.splitdrive(p)
+        if drive:
+            # The assert below probably isn't correct for the general
+            # case, but it works for Windows, which covers a lot
+            # of ground...
+            dir = fs.Dir(drive)
+            assert str(dir) == drive + os.sep, str(dir)
+
+            # Make sure that lookups with and without the drive are
+            # equivalent.
+            p = os.path.abspath(test.workpath('some/file'))
             drive, path = os.path.splitdrive(p)
-            if drive:
-                # The assert below probably isn't correct for the
-                # general case, but it works for Win32, which covers a
-                # lot of ground...
-                dir = fs.Dir(drive)
-                assert str(dir) == drive + os.sep, str(dir)
-
-            # Test Dir.scan()
-            dir = fs.Dir('ddd')
-            fs.File(string.join(['ddd', 'f1'], sep))
-            fs.File(string.join(['ddd', 'f2'], sep))
-            fs.File(string.join(['ddd', 'f3'], sep))
-            fs.Dir(string.join(['ddd', 'd1'], sep))
-            fs.Dir(string.join(['ddd', 'd1', 'f4'], sep))
-            fs.Dir(string.join(['ddd', 'd1', 'f5'], sep))
-            dir.scan()
-            kids = map(lambda x: x.path, dir.children(None))
-            kids.sort()
-            assert kids == [os.path.join('ddd', 'd1'),
-                            os.path.join('ddd', 'f1'),
-                            os.path.join('ddd', 'f2'),
-                            os.path.join('ddd', 'f3')], kids
+
+            e1 = fs.Entry(p)
+            e2 = fs.Entry(path)
+            assert e1 is e2, (e1, e2)
+            assert str(e1) is str(e2), (str(e1), str(e2))
 
         # Test for a bug in 0.04 that did not like looking up
-        # dirs with a trailing slash on Win32.
+        # dirs with a trailing slash on Windows.
         d=fs.Dir('./')
         assert d.path == '.', d.abspath
         d=fs.Dir('foo/')
@@ -985,7 +1043,7 @@ class FSTestCase(_tempdirTestCase):
         assert built_it
 
         def match(path, expect):
-            expect = string.replace(expect, '/', os.sep)
+            expect = expect.replace('/', os.sep)
             assert path == expect, "path %s != expected %s" % (path, expect)
 
         e1 = fs.Entry("d1")
@@ -1122,20 +1180,32 @@ class FSTestCase(_tempdirTestCase):
         f1.built()
         assert not f1.exists()
 
-        # For some reason, in Win32, the \x1a character terminates
+        # For some reason, in Windows, the \x1a character terminates
         # the reading of files in text mode.  This tests that
         # get_contents() returns the binary contents.
         test.write("binary_file", "Foo\x1aBar")
         f1 = fs.File(test.workpath("binary_file"))
         assert f1.get_contents() == "Foo\x1aBar", f1.get_contents()
 
+        try:
+            # TODO(1.5)
+            eval('test_string = u"Foo\x1aBar"')
+        except SyntaxError:
+            pass
+        else:
+            # This tests to make sure we can decode UTF-8 text files.
+            test.write("utf8_file", test_string.encode('utf-8'))
+            f1 = fs.File(test.workpath("utf8_file"))
+            assert eval('f1.get_text_contents() == u"Foo\x1aBar"'), \
+                   f1.get_text_contents()
+
         def nonexistent(method, s):
             try:
                 x = method(s, create = 0)
             except SCons.Errors.UserError:
                 pass
             else:
-                raise TestFailed, "did not catch expected UserError"
+                raise Exception, "did not catch expected UserError"
 
         nonexistent(fs.Entry, 'nonexistent')
         nonexistent(fs.Entry, 'nonexistent/foo')
@@ -1169,24 +1239,21 @@ class FSTestCase(_tempdirTestCase):
         f = fs.File('f_local')
         assert f._local == 0
 
-        #XXX test current() for directories
+        #XXX test_is_up_to_date() for directories
 
-        #XXX test sconsign() for directories
+        #XXX test_sconsign() for directories
 
-        #XXX test set_signature() for directories
+        #XXX test_set_signature() for directories
 
-        #XXX test build() for directories
+        #XXX test_build() for directories
 
-        #XXX test root()
+        #XXX test_root()
 
         # test Entry.get_contents()
         e = fs.Entry('does_not_exist')
-        exc_caught = 0
-        try:
-            e.get_contents()
-        except AttributeError:
-            exc_caught = 1
-        assert exc_caught, "Should have caught an AttributError"
+        c = e.get_contents()
+        assert c == "", c
+        assert e.__class__ == SCons.Node.FS.Entry
 
         test.write("file", "file\n")
         try:
@@ -1197,18 +1264,44 @@ class FSTestCase(_tempdirTestCase):
         finally:
             test.unlink("file")
 
+        # test Entry.get_text_contents()
+        e = fs.Entry('does_not_exist')
+        c = e.get_text_contents()
+        assert c == "", c
+        assert e.__class__ == SCons.Node.FS.Entry
+
+        test.write("file", "file\n")
+        try:
+            e = fs.Entry('file')
+            c = e.get_text_contents()
+            assert c == "file\n", c
+            assert e.__class__ == SCons.Node.FS.File
+        finally:
+            test.unlink("file")
+
         test.subdir("dir")
         e = fs.Entry('dir')
         c = e.get_contents()
         assert c == "", c
         assert e.__class__ == SCons.Node.FS.Dir
 
+        c = e.get_text_contents()
+        try:
+            eval('assert c == u"", c')
+        except SyntaxError:
+            assert c == ""
+
         if hasattr(os, 'symlink'):
             os.symlink('nonexistent', test.workpath('dangling_symlink'))
             e = fs.Entry('dangling_symlink')
             c = e.get_contents()
-            assert e.__class__ == SCons.Node.FS.Entry
+            assert e.__class__ == SCons.Node.FS.Entry, e.__class__
             assert c == "", c
+            c = e.get_text_contents()
+            try:
+                eval('assert c == u"", c')
+            except SyntaxError:
+                assert c == "", c
 
         test.write("tstamp", "tstamp\n")
         try:
@@ -1231,18 +1324,18 @@ class FSTestCase(_tempdirTestCase):
         assert t == 0, "expected 0, got %s" % str(t)
 
         test.subdir('tdir2')
-        d = fs.Dir('tdir2')
         f1 = test.workpath('tdir2', 'file1')
         f2 = test.workpath('tdir2', 'file2')
         test.write(f1, 'file1\n')
         test.write(f2, 'file2\n')
-        fs.File(f1)
-        fs.File(f2)
         current_time = float(int(time.time() / 2) * 2)
         t1 = current_time - 4.0
         t2 = current_time - 2.0
         os.utime(f1, (t1 - 2.0, t1))
         os.utime(f2, (t2 - 2.0, t2))
+        d = fs.Dir('tdir2')
+        fs.File(f1)
+        fs.File(f2)
         t = d.get_timestamp()
         assert t == t2, "expected %f, got %f" % (t2, t)
 
@@ -1277,9 +1370,7 @@ class FSTestCase(_tempdirTestCase):
             exc_caught = 1
         assert exc_caught, "Should have caught a TypeError"
 
-        # XXX test calc_signature()
-
-        # XXX test current()
+        # XXX test_is_up_to_date()
 
         d = fs.Dir('dir')
         r = d.remove()
@@ -1287,7 +1378,7 @@ class FSTestCase(_tempdirTestCase):
 
         f = fs.File('does_not_exist')
         r = f.remove()
-        assert r == None, r
+        assert r is None, r
 
         test.write('exists', "exists\n")
         f = fs.File('exists')
@@ -1327,20 +1418,140 @@ class FSTestCase(_tempdirTestCase):
                f.get_string(0)
         assert f.get_string(1) == 'baz', f.get_string(1)
 
+    def test_drive_letters(self):
+        """Test drive-letter look-ups"""
+
+        test = self.test
+
+        test.subdir('sub', ['sub', 'dir'])
+
+        def drive_workpath(drive, dirs, test=test):
+            x = test.workpath(*dirs)
+            drive, path = os.path.splitdrive(x)
+            return 'X:' + path
+
+        wp              = drive_workpath('X:', [''])
+
+        if wp[-1] in (os.sep, '/'):
+            tmp         = os.path.split(wp[:-1])[0]
+        else:
+            tmp         = os.path.split(wp)[0]
+
+        parent_tmp      = os.path.split(tmp)[0]
+        if parent_tmp == 'X:':
+            parent_tmp = 'X:' + os.sep
+
+        tmp_foo         = os.path.join(tmp, 'foo')
+
+        foo             = drive_workpath('X:', ['foo'])
+        foo_bar         = drive_workpath('X:', ['foo', 'bar'])
+        sub             = drive_workpath('X:', ['sub', ''])
+        sub_dir         = drive_workpath('X:', ['sub', 'dir', ''])
+        sub_dir_foo     = drive_workpath('X:', ['sub', 'dir', 'foo', ''])
+        sub_dir_foo_bar = drive_workpath('X:', ['sub', 'dir', 'foo', 'bar', ''])
+        sub_foo         = drive_workpath('X:', ['sub', 'foo', ''])
+
+        fs = SCons.Node.FS.FS()
+
+        seps = [os.sep]
+        if os.sep != '/':
+            seps = seps + ['/']
+
+        def _do_Dir_test(lpath, path_, up_path_, sep, fileSys=fs):
+            dir = fileSys.Dir(lpath.replace('/', sep))
+
+            if os.sep != '/':
+                path_ = path_.replace('/', os.sep)
+                up_path_ = up_path_.replace('/', os.sep)
+
+            def strip_slash(p):
+                if p[-1] == os.sep and len(p) > 3:
+                    p = p[:-1]
+                return p
+            path = strip_slash(path_)
+            up_path = strip_slash(up_path_)
+            name = path.split(os.sep)[-1]
+
+            assert dir.name == name, \
+                   "dir.name %s != expected name %s" % \
+                   (dir.name, name)
+            assert dir.path == path, \
+                   "dir.path %s != expected path %s" % \
+                   (dir.path, path)
+            assert str(dir) == path, \
+                   "str(dir) %s != expected path %s" % \
+                   (str(dir), path)
+            assert dir.up().path == up_path, \
+                   "dir.up().path %s != expected parent path %s" % \
+                   (dir.up().path, up_path)
+
+        save_os_path = os.path
+        save_os_sep = os.sep
+        try:
+            import ntpath
+            os.path = ntpath
+            os.sep = '\\'
+            SCons.Node.FS.initialize_do_splitdrive()
+            SCons.Node.FS.initialize_normpath_check()
+
+            for sep in seps:
+
+                def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test):
+                    return func(lpath, path_, up_path_, sep)
+
+                Dir_test('#X:',         wp,             tmp)
+                Dir_test('X:foo',       foo,            wp)
+                Dir_test('X:foo/bar',   foo_bar,        foo)
+                Dir_test('X:/foo',      'X:/foo',       'X:/')
+                Dir_test('X:/foo/bar',  'X:/foo/bar/',  'X:/foo/')
+                Dir_test('X:..',        tmp,            parent_tmp)
+                Dir_test('X:foo/..',    wp,             tmp)
+                Dir_test('X:../foo',    tmp_foo,        tmp)
+                Dir_test('X:.',         wp,             tmp)
+                Dir_test('X:./.',       wp,             tmp)
+                Dir_test('X:foo/./bar', foo_bar,        foo)
+                Dir_test('#X:../foo',   tmp_foo,        tmp)
+                Dir_test('#X:/../foo',  tmp_foo,        tmp)
+                Dir_test('#X:foo/bar',  foo_bar,        foo)
+                Dir_test('#X:/foo/bar', foo_bar,        foo)
+                Dir_test('#X:/',        wp,             tmp)
+        finally:
+            os.path = save_os_path
+            os.sep = save_os_sep
+            SCons.Node.FS.initialize_do_splitdrive()
+            SCons.Node.FS.initialize_normpath_check()
+
+    def test_target_from_source(self):
+        """Test the method for generating target nodes from sources"""
+        fs = self.fs
+
         x = fs.File('x.c')
         t = x.target_from_source('pre-', '-suf')
         assert str(t) == 'pre-x-suf', str(t)
+        assert t.__class__ == SCons.Node.FS.Entry
 
         y = fs.File('dir/y')
         t = y.target_from_source('pre-', '-suf')
         assert str(t) == os.path.join('dir', 'pre-y-suf'), str(t)
+        assert t.__class__ == SCons.Node.FS.Entry
 
         z = fs.File('zz')
         t = z.target_from_source('pre-', '-suf', lambda x: x[:-1])
         assert str(t) == 'pre-z-suf', str(t)
+        assert t.__class__ == SCons.Node.FS.Entry
+
+        d = fs.Dir('ddd')
+        t = d.target_from_source('pre-', '-suf')
+        assert str(t) == 'pre-ddd-suf', str(t)
+        assert t.__class__ == SCons.Node.FS.Entry
+
+        e = fs.Entry('eee')
+        t = e.target_from_source('pre-', '-suf')
+        assert str(t) == 'pre-eee-suf', str(t)
+        assert t.__class__ == SCons.Node.FS.Entry
 
     def test_same_name(self):
-        """Test that a local same-named file isn't found for # Dir lookup"""
+        """Test that a local same-named file isn't found for a Dir lookup"""
         test = self.test
         fs = self.fs
 
@@ -1349,8 +1560,7 @@ class FSTestCase(_tempdirTestCase):
 
         subdir = fs.Dir('subdir')
         fs.chdir(subdir, change_os_dir=1)
-        path, dir = fs._transformPath('#build/file', subdir)
-        self.fs._doLookup(SCons.Node.FS.File, path, dir)
+        self.fs._lookup('#build/file', subdir, SCons.Node.FS.File)
 
     def test_above_root(self):
         """Testing looking up a path above the root directory"""
@@ -1359,8 +1569,8 @@ class FSTestCase(_tempdirTestCase):
 
         d1 = fs.Dir('d1')
         d2 = d1.Dir('d2')
-        dirs = string.split(os.path.normpath(d2.abspath), os.sep)
-        above_path = apply(os.path.join, ['..']*len(dirs) + ['above'])
+        dirs = os.path.normpath(d2.abspath).split(os.sep)
+        above_path = os.path.join(*['..']*len(dirs) + ['above'])
         above = d2.Dir(above_path)
 
     def test_rel_path(self):
@@ -1416,7 +1626,22 @@ class FSTestCase(_tempdirTestCase):
                 d1_d2_f,        d3_d4_f,        '../../d3/d4/f',
         ]
 
-        d1.rel_path(d3)
+        if sys.platform in ('win32',):
+            x_d1        = fs.Dir(r'X:\d1')
+            x_d1_d2     = x_d1.Dir('d2')
+            y_d1        = fs.Dir(r'Y:\d1')
+            y_d1_d2     = y_d1.Dir('d2')
+            y_d2        = fs.Dir(r'Y:\d2')
+
+            win32_cases = [
+                x_d1,           x_d1,           '.',
+                x_d1,           x_d1_d2,        'd2',
+                x_d1,           y_d1,           r'Y:\d1',
+                x_d1,           y_d1_d2,        r'Y:\d1\d2',
+                x_d1,           y_d2,           r'Y:\d2',
+            ]
+
+            cases.extend(win32_cases)
 
         failed = 0
         while cases:
@@ -1431,6 +1656,22 @@ class FSTestCase(_tempdirTestCase):
                 failed = failed + 1
         assert failed == 0, "%d rel_path() cases failed" % failed
 
+    def test_proxy(self):
+        """Test a Node.FS object wrapped in a proxy instance"""
+        f1 = self.fs.File('fff')
+        class Proxy:
+            # Simplest possibly Proxy class that works for our test,
+            # this is stripped down from SCons.Util.Proxy.
+            def __init__(self, subject):
+                self.__subject = subject
+            def __getattr__(self, name):
+                return getattr(self.__subject, name)
+        p = Proxy(f1)
+        f2 = self.fs.Entry(p)
+        assert f1 is f2, (f1, f2)
+
+
+
 class DirTestCase(_tempdirTestCase):
 
     def test__morph(self):
@@ -1440,11 +1681,101 @@ class DirTestCase(_tempdirTestCase):
         x = e.get_executor()
         x.add_pre_action('pre')
         x.add_post_action('post')
-        e.must_be_a_Dir()
+        e.must_be_same(SCons.Node.FS.Dir)
         a = x.get_action_list()
         assert a[0] == 'pre', a
         assert a[2] == 'post', a
 
+    def test_subclass(self):
+        """Test looking up subclass of Dir nodes"""
+        class DirSubclass(SCons.Node.FS.Dir):
+            pass
+        sd = self.fs._lookup('special_dir', None, DirSubclass, create=1)
+        sd.must_be_same(SCons.Node.FS.Dir)
+
+    def test_get_env_scanner(self):
+        """Test the Dir.get_env_scanner() method
+        """
+        import SCons.Defaults
+        d = self.fs.Dir('ddd')
+        s = d.get_env_scanner(Environment())
+        assert s is SCons.Defaults.DirEntryScanner, s
+
+    def test_get_target_scanner(self):
+        """Test the Dir.get_target_scanner() method
+        """
+        import SCons.Defaults
+        d = self.fs.Dir('ddd')
+        s = d.get_target_scanner()
+        assert s is SCons.Defaults.DirEntryScanner, s
+
+    def test_scan(self):
+        """Test scanning a directory for in-memory entries
+        """
+        fs = self.fs
+
+        dir = fs.Dir('ddd')
+        fs.File(os.path.join('ddd', 'f1'))
+        fs.File(os.path.join('ddd', 'f2'))
+        fs.File(os.path.join('ddd', 'f3'))
+        fs.Dir(os.path.join('ddd', 'd1'))
+        fs.Dir(os.path.join('ddd', 'd1', 'f4'))
+        fs.Dir(os.path.join('ddd', 'd1', 'f5'))
+        dir.scan()
+        kids = sorted([x.path for x in dir.children(None)])
+        assert kids == [os.path.join('ddd', 'd1'),
+                        os.path.join('ddd', 'f1'),
+                        os.path.join('ddd', 'f2'),
+                        os.path.join('ddd', 'f3')], kids
+
+    def test_get_contents(self):
+        """Test getting the contents for a directory.
+        """
+        test = self.test
+
+        test.subdir('d')
+        test.write(['d', 'g'], "67890\n")
+        test.write(['d', 'f'], "12345\n")
+        test.subdir(['d','sub'])
+        test.write(['d', 'sub','h'], "abcdef\n")
+        test.subdir(['d','empty'])
+
+        d = self.fs.Dir('d')
+        g = self.fs.File(os.path.join('d', 'g'))
+        f = self.fs.File(os.path.join('d', 'f'))
+        h = self.fs.File(os.path.join('d', 'sub', 'h'))
+        e = self.fs.Dir(os.path.join('d', 'empty'))
+        s = self.fs.Dir(os.path.join('d', 'sub'))
+
+        #TODO(1.5) files = d.get_contents().split('\n')
+        files = d.get_contents().split('\n')
+
+        assert e.get_contents() == '', e.get_contents()
+        assert e.get_text_contents() == '', e.get_text_contents()
+        assert e.get_csig()+" empty" == files[0], files
+        assert f.get_csig()+" f" == files[1], files
+        assert g.get_csig()+" g" == files[2], files
+        assert s.get_csig()+" sub" == files[3], files
+
+    def test_implicit_re_scans(self):
+        """Test that adding entries causes a directory to be re-scanned
+        """
+
+        fs = self.fs
+
+        dir = fs.Dir('ddd')
+
+        fs.File(os.path.join('ddd', 'f1'))
+        dir.scan()
+        kids = sorted([x.path for x in dir.children()])
+        assert kids == [os.path.join('ddd', 'f1')], kids
+
+        fs.File(os.path.join('ddd', 'f2'))
+        dir.scan()
+        kids = sorted([x.path for x in dir.children()])
+        assert kids == [os.path.join('ddd', 'f1'),
+                        os.path.join('ddd', 'f2')], kids
+
     def test_entry_exists_on_disk(self):
         """Test the Dir.entry_exists_on_disk() method
         """
@@ -1472,12 +1803,12 @@ class DirTestCase(_tempdirTestCase):
         sub1 = bld.Dir('sub')
         sub2 = sub1.Dir('sub')
         sub3 = sub2.Dir('sub')
-        self.fs.BuildDir(bld, src, duplicate=0)
-        self.fs.BuildDir(sub2, src, duplicate=0)
+        self.fs.VariantDir(bld, src, duplicate=0)
+        self.fs.VariantDir(sub2, src, duplicate=0)
 
         def check(result, expect):
-            result = map(str, result)
-            expect = map(os.path.normpath, expect)
+            result = list(map(str, result))
+            expect = list(map(os.path.normpath, expect))
             assert result == expect, result
 
         s = src.srcdir_list()
@@ -1495,7 +1826,7 @@ class DirTestCase(_tempdirTestCase):
         s = sub3.srcdir_list()
         check(s, ['src/sub', 'src/sub/sub/sub'])
 
-        self.fs.BuildDir('src/b1/b2', 'src')
+        self.fs.VariantDir('src/b1/b2', 'src')
         b1 = src.Dir('b1')
         b1_b2 = b1.Dir('b2')
         b1_b2_b1 = b1_b2.Dir('b1')
@@ -1512,10 +1843,10 @@ class DirTestCase(_tempdirTestCase):
         check(s, ['src/b1'])
 
         s = b1_b2_b1_b2.srcdir_list()
-        check(s, [])
+        check(s, ['src/b1/b2'])
 
         s = b1_b2_b1_b2_sub.srcdir_list()
-        check(s, [])
+        check(s, ['src/b1/b2/sub'])
 
     def test_srcdir_duplicate(self):
         """Test the Dir.srcdir_duplicate() method
@@ -1527,7 +1858,7 @@ class DirTestCase(_tempdirTestCase):
 
         bld0 = self.fs.Dir('bld0')
         src0 = self.fs.Dir('src0')
-        self.fs.BuildDir(bld0, src0, duplicate=0)
+        self.fs.VariantDir(bld0, src0, duplicate=0)
 
         n = bld0.srcdir_duplicate('does_not_exist')
         assert n is None, n
@@ -1542,7 +1873,7 @@ class DirTestCase(_tempdirTestCase):
 
         bld1 = self.fs.Dir('bld1')
         src1 = self.fs.Dir('src1')
-        self.fs.BuildDir(bld1, src1, duplicate=1)
+        self.fs.VariantDir(bld1, src1, duplicate=1)
 
         n = bld1.srcdir_duplicate('does_not_exist')
         assert n is None, n
@@ -1567,25 +1898,21 @@ class DirTestCase(_tempdirTestCase):
 
         bld0 = self.fs.Dir('bld0')
         src0 = self.fs.Dir('src0')
-        self.fs.BuildDir(bld0, src0, duplicate=0)
+        self.fs.VariantDir(bld0, src0, duplicate=0)
 
         derived_f = src0.File('derived-f')
         derived_f.is_derived = return_true
-        pseudo_f = src0.File('pseudo-f')
-        pseudo_f.is_pseudo_derived = return_true
         exists_f = src0.File('exists-f')
         exists_f.exists = return_true
 
         derived_e = src0.Entry('derived-e')
         derived_e.is_derived = return_true
-        pseudo_e = src0.Entry('pseudo-e')
-        pseudo_e.is_pseudo_derived = return_true
         exists_e = src0.Entry('exists-e')
         exists_e.exists = return_true
 
         def check(result, expect):
-            result = map(str, result)
-            expect = map(os.path.normpath, expect)
+            result = list(map(str, result))
+            expect = list(map(os.path.normpath, expect))
             assert result == expect, result
 
         # First check from the source directory.
@@ -1594,8 +1921,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = src0.srcdir_find_file('derived-f')
         check(n, ['src0/derived-f', 'src0'])
-        n = src0.srcdir_find_file('pseudo-f')
-        check(n, ['src0/pseudo-f', 'src0'])
         n = src0.srcdir_find_file('exists-f')
         check(n, ['src0/exists-f', 'src0'])
         n = src0.srcdir_find_file('on-disk-f1')
@@ -1603,21 +1928,17 @@ class DirTestCase(_tempdirTestCase):
 
         n = src0.srcdir_find_file('derived-e')
         check(n, ['src0/derived-e', 'src0'])
-        n = src0.srcdir_find_file('pseudo-e')
-        check(n, ['src0/pseudo-e', 'src0'])
         n = src0.srcdir_find_file('exists-e')
         check(n, ['src0/exists-e', 'src0'])
         n = src0.srcdir_find_file('on-disk-e1')
         check(n, ['src0/on-disk-e1', 'src0'])
 
-        # Now check from the build directory.
+        # Now check from the variant directory.
         n = bld0.srcdir_find_file('does_not_exist')
         assert n == (None, None), n
 
         n = bld0.srcdir_find_file('derived-f')
         check(n, ['src0/derived-f', 'bld0'])
-        n = bld0.srcdir_find_file('pseudo-f')
-        check(n, ['src0/pseudo-f', 'bld0'])
         n = bld0.srcdir_find_file('exists-f')
         check(n, ['src0/exists-f', 'bld0'])
         n = bld0.srcdir_find_file('on-disk-f2')
@@ -1625,8 +1946,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = bld0.srcdir_find_file('derived-e')
         check(n, ['src0/derived-e', 'bld0'])
-        n = bld0.srcdir_find_file('pseudo-e')
-        check(n, ['src0/pseudo-e', 'bld0'])
         n = bld0.srcdir_find_file('exists-e')
         check(n, ['src0/exists-e', 'bld0'])
         n = bld0.srcdir_find_file('on-disk-e2')
@@ -1640,19 +1959,15 @@ class DirTestCase(_tempdirTestCase):
 
         bld1 = self.fs.Dir('bld1')
         src1 = self.fs.Dir('src1')
-        self.fs.BuildDir(bld1, src1, duplicate=1)
+        self.fs.VariantDir(bld1, src1, duplicate=1)
 
         derived_f = src1.File('derived-f')
         derived_f.is_derived = return_true
-        pseudo_f = src1.File('pseudo-f')
-        pseudo_f.is_pseudo_derived = return_true
         exists_f = src1.File('exists-f')
         exists_f.exists = return_true
 
         derived_e = src1.Entry('derived-e')
         derived_e.is_derived = return_true
-        pseudo_e = src1.Entry('pseudo-e')
-        pseudo_e.is_pseudo_derived = return_true
         exists_e = src1.Entry('exists-e')
         exists_e.exists = return_true
 
@@ -1662,8 +1977,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = src1.srcdir_find_file('derived-f')
         check(n, ['src1/derived-f', 'src1'])
-        n = src1.srcdir_find_file('pseudo-f')
-        check(n, ['src1/pseudo-f', 'src1'])
         n = src1.srcdir_find_file('exists-f')
         check(n, ['src1/exists-f', 'src1'])
         n = src1.srcdir_find_file('on-disk-f1')
@@ -1671,21 +1984,17 @@ class DirTestCase(_tempdirTestCase):
 
         n = src1.srcdir_find_file('derived-e')
         check(n, ['src1/derived-e', 'src1'])
-        n = src1.srcdir_find_file('pseudo-e')
-        check(n, ['src1/pseudo-e', 'src1'])
         n = src1.srcdir_find_file('exists-e')
         check(n, ['src1/exists-e', 'src1'])
         n = src1.srcdir_find_file('on-disk-e1')
         check(n, ['src1/on-disk-e1', 'src1'])
 
-        # Now check from the build directory.
+        # Now check from the variant directory.
         n = bld1.srcdir_find_file('does_not_exist')
         assert n == (None, None), n
 
         n = bld1.srcdir_find_file('derived-f')
         check(n, ['bld1/derived-f', 'src1'])
-        n = bld1.srcdir_find_file('pseudo-f')
-        check(n, ['bld1/pseudo-f', 'src1'])
         n = bld1.srcdir_find_file('exists-f')
         check(n, ['bld1/exists-f', 'src1'])
         n = bld1.srcdir_find_file('on-disk-f2')
@@ -1693,8 +2002,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = bld1.srcdir_find_file('derived-e')
         check(n, ['bld1/derived-e', 'src1'])
-        n = bld1.srcdir_find_file('pseudo-e')
-        check(n, ['bld1/pseudo-e', 'src1'])
         n = bld1.srcdir_find_file('exists-e')
         check(n, ['bld1/exists-e', 'src1'])
         n = bld1.srcdir_find_file('on-disk-e2')
@@ -1744,10 +2051,6 @@ class EntryTestCase(_tempdirTestCase):
         e1.rfile()
         assert e1.__class__ is SCons.Node.FS.File, e1.__class__
 
-        e2 = fs.Entry('e2')
-        e2.get_found_includes(None, None, None)
-        assert e2.__class__ is SCons.Node.FS.File, e2.__class__
-
         test.subdir('e3d')
         test.write('e3f', "e3f\n")
 
@@ -1760,12 +2063,8 @@ class EntryTestCase(_tempdirTestCase):
         assert e3f.__class__ is SCons.Node.FS.File, e3f.__class__
 
         e3n = fs.Entry('e3n')
-        exc_caught = None
-        try:
-            e3n.get_contents()
-        except AttributeError:
-            exc_caught = 1
-        assert exc_caught, "did not catch expected AttributeError"
+        e3n.get_contents()
+        assert e3n.__class__ is SCons.Node.FS.Entry, e3n.__class__
 
         test.subdir('e4d')
         test.write('e4f', "e4f\n")
@@ -1800,26 +2099,6 @@ class EntryTestCase(_tempdirTestCase):
         test.subdir('e5d')
         test.write('e5f', "e5f\n")
 
-        e5d = fs.Entry('e5d')
-        sig = e5d.calc_signature(MyCalc(555))
-        assert e5d.__class__ is SCons.Node.FS.Dir, e5d.__class__
-        # Node has builder (MkDirBuilder), so executor will calculate
-        # the build signature.
-        assert sig == 777, sig
-
-        e5f = fs.Entry('e5f')
-        sig = e5f.calc_signature(MyCalc(666))
-        assert e5f.__class__ is SCons.Node.FS.File, e5f.__class__
-        # This node has no builder, so it just calculates the
-        # signature once: the source content signature.
-        assert sig == 888, sig
-
-        e5n = fs.Entry('e5n')
-        sig = e5n.calc_signature(MyCalc(777))
-        assert e5n.__class__ is SCons.Node.FS.File, e5n.__class__
-        # Doesn't exist, no sources, and no builder: no sig
-        assert sig is None, sig
-
     def test_Entry_Entry_lookup(self):
         """Test looking up an Entry within another Entry"""
         self.fs.Entry('#topdir')
@@ -1829,6 +2108,13 @@ class EntryTestCase(_tempdirTestCase):
 
 class FileTestCase(_tempdirTestCase):
 
+    def test_subclass(self):
+        """Test looking up subclass of File nodes"""
+        class FileSubclass(SCons.Node.FS.File):
+            pass
+        sd = self.fs._lookup('special_file', None, FileSubclass, create=1)
+        sd.must_be_same(SCons.Node.FS.File)
+
     def test_Dirs(self):
         """Test the File.Dirs() method"""
         fff = self.fs.File('subdir/fff')
@@ -1838,7 +2124,7 @@ class FileTestCase(_tempdirTestCase):
         d1 = self.fs.Dir('subdir/d1')
         d2 = self.fs.Dir('subdir/d2')
         dirs = fff.Dirs(['d1', 'd2'])
-        assert dirs == [d1, d2], map(str, dirs)
+        assert dirs == [d1, d2], list(map(str, dirs))
 
     def test_exists(self):
         """Test the File.exists() method"""
@@ -1856,7 +2142,7 @@ class FileTestCase(_tempdirTestCase):
         assert src_f1.exists(), "%s apparently does not exist?" % src_f1
 
         test.subdir('build')
-        fs.BuildDir('build', 'src')
+        fs.VariantDir('build', 'src')
         build_f1 = fs.File('build/f1')
 
         assert build_f1.exists(), "%s did not realize that %s exists" % (build_f1, src_f1)
@@ -1873,6 +2159,338 @@ class FileTestCase(_tempdirTestCase):
 
 
 
+class GlobTestCase(_tempdirTestCase):
+    def setUp(self):
+        _tempdirTestCase.setUp(self)
+
+        fs = SCons.Node.FS.FS()
+        self.fs = fs
+
+        # Make entries on disk that will not have Nodes, so we can verify
+        # the behavior of looking for things on disk.
+        self.test.write('disk-bbb', "disk-bbb\n")
+        self.test.write('disk-aaa', "disk-aaa\n")
+        self.test.write('disk-ccc', "disk-ccc\n")
+        self.test.write('#disk-hash', "#disk-hash\n")
+        self.test.subdir('disk-sub')
+        self.test.write(['disk-sub', 'disk-ddd'], "disk-sub/disk-ddd\n")
+        self.test.write(['disk-sub', 'disk-eee'], "disk-sub/disk-eee\n")
+        self.test.write(['disk-sub', 'disk-fff'], "disk-sub/disk-fff\n")
+
+        # Make some entries that have both Nodes and on-disk entries,
+        # so we can verify what we do with
+        self.test.write('both-aaa', "both-aaa\n")
+        self.test.write('both-bbb', "both-bbb\n")
+        self.test.write('both-ccc', "both-ccc\n")
+        self.test.write('#both-hash', "#both-hash\n")
+        self.test.subdir('both-sub1')
+        self.test.write(['both-sub1', 'both-ddd'], "both-sub1/both-ddd\n")
+        self.test.write(['both-sub1', 'both-eee'], "both-sub1/both-eee\n")
+        self.test.write(['both-sub1', 'both-fff'], "both-sub1/both-fff\n")
+        self.test.subdir('both-sub2')
+        self.test.write(['both-sub2', 'both-ddd'], "both-sub2/both-ddd\n")
+        self.test.write(['both-sub2', 'both-eee'], "both-sub2/both-eee\n")
+        self.test.write(['both-sub2', 'both-fff'], "both-sub2/both-fff\n")
+
+        self.both_aaa = fs.File('both-aaa')
+        self.both_bbb = fs.File('both-bbb')
+        self.both_ccc = fs.File('both-ccc')
+        self._both_hash = fs.File('./#both-hash')
+        self.both_sub1 = fs.Dir('both-sub1')
+        self.both_sub1_both_ddd = self.both_sub1.File('both-ddd')
+        self.both_sub1_both_eee = self.both_sub1.File('both-eee')
+        self.both_sub1_both_fff = self.both_sub1.File('both-fff')
+        self.both_sub2 = fs.Dir('both-sub2')
+        self.both_sub2_both_ddd = self.both_sub2.File('both-ddd')
+        self.both_sub2_both_eee = self.both_sub2.File('both-eee')
+        self.both_sub2_both_fff = self.both_sub2.File('both-fff')
+
+        # Make various Nodes (that don't have on-disk entries) so we
+        # can verify how we match them.
+        self.ggg = fs.File('ggg')
+        self.hhh = fs.File('hhh')
+        self.iii = fs.File('iii')
+        self._hash = fs.File('./#hash')
+        self.subdir1 = fs.Dir('subdir1')
+        self.subdir1_lll = self.subdir1.File('lll')
+        self.subdir1_jjj = self.subdir1.File('jjj')
+        self.subdir1_kkk = self.subdir1.File('kkk')
+        self.subdir2 = fs.Dir('subdir2')
+        self.subdir2_lll = self.subdir2.File('lll')
+        self.subdir2_kkk = self.subdir2.File('kkk')
+        self.subdir2_jjj = self.subdir2.File('jjj')
+        self.sub = fs.Dir('sub')
+        self.sub_dir3 = self.sub.Dir('dir3')
+        self.sub_dir3_kkk = self.sub_dir3.File('kkk')
+        self.sub_dir3_jjj = self.sub_dir3.File('jjj')
+        self.sub_dir3_lll = self.sub_dir3.File('lll')
+
+
+    def do_cases(self, cases, **kwargs):
+
+        # First, execute all of the cases with string=True and verify
+        # that we get the expected strings returned.  We do this first
+        # so the Glob() calls don't add Nodes to the self.fs file system
+        # hierarchy.
+
+        import copy
+        strings_kwargs = copy.copy(kwargs)
+        strings_kwargs['strings'] = True
+        for input, string_expect, node_expect in cases:
+            r = sorted(self.fs.Glob(input, **strings_kwargs))
+            assert r == string_expect, "Glob(%s, strings=True) expected %s, got %s" % (input, string_expect, r)
+
+        # Now execute all of the cases without string=True and look for
+        # the expected Nodes to be returned.  If we don't have a list of
+        # actual expected Nodes, that means we're expecting a search for
+        # on-disk-only files to have returned some newly-created nodes.
+        # Verify those by running the list through str() before comparing
+        # them with the expected list of strings.
+        for input, string_expect, node_expect in cases:
+            r = self.fs.Glob(input, **kwargs)
+            if node_expect:
+                r.sort(lambda a,b: cmp(a.path, b.path))
+                result = []
+                for n in node_expect:
+                    if isinstance(n, str):
+                        n = self.fs.Entry(n)
+                    result.append(n)
+                fmt = lambda n: "%s %s" % (repr(n), repr(str(n)))
+            else:
+                r = sorted(map(str, r))
+                result = string_expect
+                fmt = lambda n: n
+            if r != result:
+                import pprint
+                print "Glob(%s) expected:" % repr(input)
+                pprint.pprint(list(map(fmt, result)))
+                print "Glob(%s) got:" % repr(input)
+                pprint.pprint(list(map(fmt, r)))
+                self.fail()
+
+    def test_exact_match(self):
+        """Test globbing for exact Node matches"""
+        join = os.path.join
+
+        cases = (
+            ('ggg',         ['ggg'],                    [self.ggg]),
+
+            ('subdir1',     ['subdir1'],                [self.subdir1]),
+
+            ('subdir1/jjj', [join('subdir1', 'jjj')],   [self.subdir1_jjj]),
+
+            ('disk-aaa',    ['disk-aaa'],               None),
+
+            ('disk-sub',    ['disk-sub'],               None),
+
+            ('both-aaa',    ['both-aaa'],               []),
+        )
+
+        self.do_cases(cases)
+
+    def test_subdir_matches(self):
+        """Test globbing for exact Node matches in subdirectories"""
+        join = os.path.join
+
+        cases = (
+            ('*/jjj',
+             [join('subdir1', 'jjj'), join('subdir2', 'jjj')],
+             [self.subdir1_jjj, self.subdir2_jjj]),
+
+            ('*/disk-ddd',
+             [join('disk-sub', 'disk-ddd')],
+             None),
+        )
+
+        self.do_cases(cases)
+
+    def test_asterisk1(self):
+        """Test globbing for simple asterisk Node matches (1)"""
+        cases = (
+            ('h*',
+             ['hhh'],
+             [self.hhh]),
+
+            ('*',
+             ['#both-hash', '#hash',
+              'both-aaa', 'both-bbb', 'both-ccc',
+              'both-sub1', 'both-sub2',
+              'ggg', 'hhh', 'iii',
+              'sub', 'subdir1', 'subdir2'],
+             [self._both_hash, self._hash,
+              self.both_aaa, self.both_bbb, self.both_ccc, 'both-hash',
+              self.both_sub1, self.both_sub2,
+              self.ggg, 'hash', self.hhh, self.iii,
+              self.sub, self.subdir1, self.subdir2]),
+        )
+
+        self.do_cases(cases, ondisk=False)
+
+    def test_asterisk2(self):
+        """Test globbing for simple asterisk Node matches (2)"""
+        cases = (
+            ('disk-b*',
+             ['disk-bbb'],
+             None),
+
+            ('*',
+             ['#both-hash', '#disk-hash', '#hash',
+              'both-aaa', 'both-bbb', 'both-ccc',
+              'both-sub1', 'both-sub2',
+              'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub',
+              'ggg', 'hhh', 'iii',
+              'sub', 'subdir1', 'subdir2'],
+             ['./#both-hash', './#disk-hash', './#hash',
+              'both-aaa', 'both-bbb', 'both-ccc', 'both-hash',
+              'both-sub1', 'both-sub2',
+              'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub',
+              'ggg', 'hash', 'hhh', 'iii',
+              'sub', 'subdir1', 'subdir2']),
+        )
+
+        self.do_cases(cases)
+
+    def test_question_mark(self):
+        """Test globbing for simple question-mark Node matches"""
+        join = os.path.join
+
+        cases = (
+            ('ii?',
+             ['iii'],
+             [self.iii]),
+
+            ('both-sub?/both-eee',
+             [join('both-sub1', 'both-eee'), join('both-sub2', 'both-eee')],
+             [self.both_sub1_both_eee, self.both_sub2_both_eee]),
+
+            ('subdir?/jjj',
+             [join('subdir1', 'jjj'), join('subdir2', 'jjj')],
+             [self.subdir1_jjj, self.subdir2_jjj]),
+
+            ('disk-cc?',
+             ['disk-ccc'],
+             None),
+        )
+
+        self.do_cases(cases)
+
+    def test_does_not_exist(self):
+        """Test globbing for things that don't exist"""
+
+        cases = (
+            ('does_not_exist',  [], []),
+            ('no_subdir/*',     [], []),
+            ('subdir?/no_file', [], []),
+        )
+
+        self.do_cases(cases)
+
+    def test_subdir_asterisk(self):
+        """Test globbing for asterisk Node matches in subdirectories"""
+        join = os.path.join
+
+        cases = (
+            ('*/k*',
+             [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+             [self.subdir1_kkk, self.subdir2_kkk]),
+
+            ('both-sub?/*',
+             [join('both-sub1', 'both-ddd'),
+              join('both-sub1', 'both-eee'),
+              join('both-sub1', 'both-fff'),
+              join('both-sub2', 'both-ddd'),
+              join('both-sub2', 'both-eee'),
+              join('both-sub2', 'both-fff')],
+             [self.both_sub1_both_ddd,
+              self.both_sub1_both_eee,
+              self.both_sub1_both_fff,
+              self.both_sub2_both_ddd,
+              self.both_sub2_both_eee,
+              self.both_sub2_both_fff],
+             ),
+
+            ('subdir?/*',
+             [join('subdir1', 'jjj'),
+              join('subdir1', 'kkk'),
+              join('subdir1', 'lll'),
+              join('subdir2', 'jjj'),
+              join('subdir2', 'kkk'),
+              join('subdir2', 'lll')],
+             [self.subdir1_jjj, self.subdir1_kkk, self.subdir1_lll,
+              self.subdir2_jjj, self.subdir2_kkk, self.subdir2_lll]),
+
+            ('sub/*/*',
+             [join('sub', 'dir3', 'jjj'),
+              join('sub', 'dir3', 'kkk'),
+              join('sub', 'dir3', 'lll')],
+             [self.sub_dir3_jjj, self.sub_dir3_kkk, self.sub_dir3_lll]),
+
+            ('*/k*',
+             [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+             None),
+
+            ('subdir?/*',
+             [join('subdir1', 'jjj'),
+              join('subdir1', 'kkk'),
+              join('subdir1', 'lll'),
+              join('subdir2', 'jjj'),
+              join('subdir2', 'kkk'),
+              join('subdir2', 'lll')],
+             None),
+
+            ('sub/*/*',
+             [join('sub', 'dir3', 'jjj'),
+              join('sub', 'dir3', 'kkk'),
+              join('sub', 'dir3', 'lll')],
+             None),
+        )
+
+        self.do_cases(cases)
+
+    def test_subdir_question(self):
+        """Test globbing for question-mark Node matches in subdirectories"""
+        join = os.path.join
+
+        cases = (
+            ('*/?kk',
+             [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+             [self.subdir1_kkk, self.subdir2_kkk]),
+
+            ('subdir?/l?l',
+             [join('subdir1', 'lll'), join('subdir2', 'lll')],
+             [self.subdir1_lll, self.subdir2_lll]),
+
+            ('*/disk-?ff',
+             [join('disk-sub', 'disk-fff')],
+             None),
+
+            ('subdir?/l?l',
+             [join('subdir1', 'lll'), join('subdir2', 'lll')],
+             None),
+        )
+
+        self.do_cases(cases)
+
+    def test_sort(self):
+        """Test whether globbing sorts"""
+        join = os.path.join
+        # At least sometimes this should return out-of-order items
+        # if Glob doesn't sort.
+        # It's not a very good test though since it depends on the
+        # order returned by glob, which might already be sorted.
+        g = self.fs.Glob('disk-sub/*', strings=True)
+        expect = [
+            os.path.join('disk-sub', 'disk-ddd'),
+            os.path.join('disk-sub', 'disk-eee'),
+            os.path.join('disk-sub', 'disk-fff'),
+        ]
+        assert g == expect, str(g) + " is not sorted, but should be!"
+
+        g = self.fs.Glob('disk-*', strings=True)
+        expect = [ 'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub' ]
+        assert g == expect, str(g) + " is not sorted, but should be!"
+
+
 class RepositoryTestCase(_tempdirTestCase):
 
     def setUp(self):
@@ -1907,7 +2525,7 @@ class RepositoryTestCase(_tempdirTestCase):
         ]
 
         rep = self.fs.Dir('#').getRepositories()
-        r = map(lambda x, np=os.path.normpath: np(str(x)), rep)
+        r = [os.path.normpath(str(x)) for x in rep]
         assert r == expect, r
 
     def test_get_all_rdirs(self):
@@ -1929,9 +2547,77 @@ class RepositoryTestCase(_tempdirTestCase):
         ]
 
         rep = self.fs.Dir('#').get_all_rdirs()
-        r = map(lambda x, np=os.path.normpath: np(str(x)), rep)
+        r = [os.path.normpath(str(x)) for x in rep]
         assert r == expect, r
 
+    def test_rentry(self):
+        """Test the Base.entry() method"""
+        return_true = lambda: 1
+        return_false = lambda: 0
+
+        d1 = self.fs.Dir('d1')
+        d2 = self.fs.Dir('d2')
+        d3 = self.fs.Dir('d3')
+
+        e1 = self.fs.Entry('e1')
+        e2 = self.fs.Entry('e2')
+        e3 = self.fs.Entry('e3')
+
+        f1 = self.fs.File('f1')
+        f2 = self.fs.File('f2')
+        f3 = self.fs.File('f3')
+
+        self.test.write([self.rep1, 'd2'], "")
+        self.test.subdir([self.rep2, 'd3'])
+        self.test.write([self.rep3, 'd3'], "")
+
+        self.test.write([self.rep1, 'e2'], "")
+        self.test.subdir([self.rep2, 'e3'])
+        self.test.write([self.rep3, 'e3'], "")
+
+        self.test.write([self.rep1, 'f2'], "")
+        self.test.subdir([self.rep2, 'f3'])
+        self.test.write([self.rep3, 'f3'], "")
+
+        r = d1.rentry()
+        assert r is d1, r
+
+        r = d2.rentry()
+        assert not r is d2, r
+        r = str(r)
+        assert r == os.path.join(self.rep1, 'd2'), r
+
+        r = d3.rentry()
+        assert not r is d3, r
+        r = str(r)
+        assert r == os.path.join(self.rep2, 'd3'), r
+
+        r = e1.rentry()
+        assert r is e1, r
+
+        r = e2.rentry()
+        assert not r is e2, r
+        r = str(r)
+        assert r == os.path.join(self.rep1, 'e2'), r
+
+        r = e3.rentry()
+        assert not r is e3, r
+        r = str(r)
+        assert r == os.path.join(self.rep2, 'e3'), r
+
+        r = f1.rentry()
+        assert r is f1, r
+
+        r = f2.rentry()
+        assert not r is f2, r
+        r = str(r)
+        assert r == os.path.join(self.rep1, 'f2'), r
+
+        r = f3.rentry()
+        assert not r is f3, r
+        r = str(r)
+        assert r == os.path.join(self.rep2, 'f3'), r
+
     def test_rdir(self):
         """Test the Dir.rdir() method"""
         return_true = lambda: 1
@@ -2042,26 +2728,26 @@ class RepositoryTestCase(_tempdirTestCase):
         rep2_sub_d1 = fs.Dir(test.workpath('rep2', 'sub', 'd1'))
         rep3_sub_d1 = fs.Dir(test.workpath('rep3', 'sub', 'd1'))
 
-        r = fs.Rfindalldirs(d1, fs.Top)
-        assert r == [d1], map(str, r)
+        r = fs.Top.Rfindalldirs((d1,))
+        assert r == [d1], list(map(str, r))
 
-        r = fs.Rfindalldirs([d1, d2], fs.Top)
-        assert r == [d1, d2], map(str, r)
+        r = fs.Top.Rfindalldirs((d1, d2))
+        assert r == [d1, d2], list(map(str, r))
 
-        r = fs.Rfindalldirs('d1', fs.Top)
-        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r)
+        r = fs.Top.Rfindalldirs(('d1',))
+        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(map(str, r))
 
-        r = fs.Rfindalldirs('#d1', fs.Top)
-        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r)
+        r = fs.Top.Rfindalldirs(('#d1',))
+        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(map(str, r))
 
-        r = fs.Rfindalldirs('d1', sub)
-        assert r == [sub_d1, rep1_sub_d1, rep2_sub_d1, rep3_sub_d1], map(str, r)
+        r = sub.Rfindalldirs(('d1',))
+        assert r == [sub_d1, rep1_sub_d1, rep2_sub_d1, rep3_sub_d1], list(map(str, r))
 
-        r = fs.Rfindalldirs('#d1', sub)
-        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r)
+        r = sub.Rfindalldirs(('#d1',))
+        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(map(str, r))
 
-        r = fs.Rfindalldirs(['d1', d2], fs.Top)
-        assert r == [d1, rep1_d1, rep2_d1, rep3_d1, d2], map(str, r)
+        r = fs.Top.Rfindalldirs(('d1', d2))
+        assert r == [d1, rep1_d1, rep2_d1, rep3_d1, d2], list(map(str, r))
 
     def test_rexists(self):
         """Test the Entry.rexists() method"""
@@ -2072,7 +2758,7 @@ class RepositoryTestCase(_tempdirTestCase):
         test.write([self.rep2, "i_exist"], "\n")
         test.write(["work", "i_exist_too"], "\n")
 
-        fs.BuildDir('build', '.')
+        fs.VariantDir('build', '.')
 
         f = fs.File(test.workpath("work", "i_do_not_exist"))
         assert not f.rexists()
@@ -2121,9 +2807,49 @@ class RepositoryTestCase(_tempdirTestCase):
         finally:
             test.unlink(["rep3", "contents"])
 
-    #def test calc_signature(self):
+    def test_get_text_contents(self):
+        """Ensure get_text_contents() returns text contents from
+        Repositories"""
+        fs = self.fs
+        test = self.test
 
-    #def test current(self):
+        # Use a test string that has a file terminator in it to make
+        # sure we read the entire file, regardless of its contents.
+        try:
+            eval('test_string = u"Con\x1aTents\n"')
+        except SyntaxError:
+            import UserString
+            class FakeUnicodeString(UserString.UserString):
+                def encode(self, encoding):
+                    return str(self)
+            test_string = FakeUnicodeString("Con\x1aTents\n")
+
+
+        # Test with ASCII.
+        test.write(["rep3", "contents"], test_string.encode('ascii'))
+        try:
+            c = fs.File("contents").get_text_contents()
+            assert test_string == c, "got %s" % repr(c)
+        finally:
+            test.unlink(["rep3", "contents"])
+
+        # Test with utf-8
+        test.write(["rep3", "contents"], test_string.encode('utf-8'))
+        try:
+            c = fs.File("contents").get_text_contents()
+            assert test_string == c, "got %s" % repr(c)
+        finally:
+            test.unlink(["rep3", "contents"])
+
+        # Test with utf-16
+        test.write(["rep3", "contents"], test_string.encode('utf-16'))
+        try:
+            c = fs.File("contents").get_text_contents()
+            assert test_string == c, "got %s" % repr(c)
+        finally:
+            test.unlink(["rep3", "contents"])
+
+    #def test_is_up_to_date(self):
 
 
 
@@ -2132,6 +2858,7 @@ class find_fileTestCase(unittest.TestCase):
         """Testing find_file function"""
         test = TestCmd(workdir = '')
         test.write('./foo', 'Some file\n')
+        test.write('./foo2', 'Another file\n')
         test.subdir('same')
         test.subdir('bar')
         test.write(['bar', 'on_disk'], 'Another file\n')
@@ -2146,16 +2873,16 @@ class find_fileTestCase(unittest.TestCase):
         node_pseudo = fs.File(test.workpath('pseudo'))
         node_pseudo.set_src_builder(1) # Any non-zero value.
 
-        paths = map(fs.Dir, ['.', 'same', './bar'])
+        paths = tuple(map(fs.Dir, ['.', 'same', './bar']))
         nodes = [SCons.Node.FS.find_file('foo', paths)]
         nodes.append(SCons.Node.FS.find_file('baz', paths))
         nodes.append(SCons.Node.FS.find_file('pseudo', paths))
         nodes.append(SCons.Node.FS.find_file('same', paths))
 
-        file_names = map(str, nodes)
-        file_names = map(os.path.normpath, file_names)
+        file_names = list(map(str, nodes))
+        file_names = list(map(os.path.normpath, file_names))
         expect = ['./foo', './bar/baz', './pseudo', './bar/same']
-        expect = map(os.path.normpath, expect)
+        expect = list(map(os.path.normpath, expect))
         assert file_names == expect, file_names
 
         # Make sure we don't blow up if there's already a File in place
@@ -2170,19 +2897,18 @@ class find_fileTestCase(unittest.TestCase):
         try:
             sio = StringIO.StringIO()
             sys.stdout = sio
-            SCons.Node.FS.find_file('foo', paths, verbose="xyz")
-            expect = "  xyz: looking for 'foo' in '.' ...\n" + \
-                     "  xyz: ... FOUND 'foo' in '.'\n"
+            SCons.Node.FS.find_file('foo2', paths, verbose="xyz")
+            expect = "  xyz: looking for 'foo2' in '.' ...\n" + \
+                     "  xyz: ... FOUND 'foo2' in '.'\n"
             c = sio.getvalue()
             assert c == expect, c
 
             sio = StringIO.StringIO()
             sys.stdout = sio
-            SCons.Node.FS.find_file('baz', paths, verbose=1)
-            expect = "  find_file: looking for 'baz' in '.' ...\n" + \
-                     "  find_file: looking for 'baz' in 'same' ...\n" + \
-                     "  find_file: looking for 'baz' in 'bar' ...\n" + \
-                     "  find_file: ... FOUND 'baz' in 'bar'\n"
+            SCons.Node.FS.find_file('baz2', paths, verbose=1)
+            expect = "  find_file: looking for 'baz2' in '.' ...\n" + \
+                     "  find_file: looking for 'baz2' in 'same' ...\n" + \
+                     "  find_file: looking for 'baz2' in 'bar' ...\n"
             c = sio.getvalue()
             assert c == expect, c
 
@@ -2208,7 +2934,7 @@ class StringDirTestCase(unittest.TestCase):
         fs = SCons.Node.FS.FS(test.workpath(''))
 
         d = fs.Dir('sub', '.')
-        assert str(d) == 'sub'
+        assert str(d) == 'sub', str(d)
         assert d.exists()
         f = fs.File('file', 'sub')
         assert str(f) == os.path.join('sub', 'file')
@@ -2224,8 +2950,7 @@ class stored_infoTestCase(unittest.TestCase):
         d = fs.Dir('sub')
         f = fs.File('file1', d)
         bi = f.get_stored_info()
-        assert bi.ninfo.timestamp == 0, bi.ninfo.timestamp
-        assert bi.ninfo.size == None, bi.ninfo.size
+        assert hasattr(bi, 'ninfo')
 
         class MySConsign:
             class Null:
@@ -2331,7 +3056,10 @@ class prepareTestCase(unittest.TestCase):
         class MkdirAction(Action):
             def __init__(self, dir_made):
                 self.dir_made = dir_made
-            def __call__(self, target, source, env, errfunc):
+            def __call__(self, target, source, env, executor=None):
+                if executor:
+                    target = executor.get_all_targets()
+                    source = executor.get_all_sources()
                 self.dir_made.extend(target)
 
         dir_made = []
@@ -2351,6 +3079,8 @@ class prepareTestCase(unittest.TestCase):
         dir = fs.Dir("dir")
         dir.prepare()
 
+
+
 class SConstruct_dirTestCase(unittest.TestCase):
     def runTest(self):
         """Test setting the SConstruct directory"""
@@ -2359,199 +3089,18 @@ class SConstruct_dirTestCase(unittest.TestCase):
         fs.set_SConstruct_dir(fs.Dir('xxx'))
         assert fs.SConstruct_dir.path == 'xxx'
 
-class CacheDirTestCase(unittest.TestCase):
-    def runTest(self):
-        """Test CacheDir functionality"""
-        test = TestCmd(workdir='')
-
-        global built_it
-
-        fs = SCons.Node.FS.FS()
-        assert fs.CachePath is None, fs.CachePath
-        assert fs.cache_force is None, fs.cache_force
-        assert fs.cache_show is None, fs.cache_show
-
-        fs.CacheDir('cache')
-        assert fs.CachePath == 'cache', fs.CachePath
-
-        save_CacheRetrieve = SCons.Node.FS.CacheRetrieve
-        self.retrieved = []
-        def retrieve_succeed(target, source, env, self=self, execute=1):
-            self.retrieved.append(target)
-            return 0
-        def retrieve_fail(target, source, env, self=self, execute=1):
-            self.retrieved.append(target)
-            return 1
-
-        f1 = fs.File("cd.f1")
-        f1.builder_set(Builder(fs.File))
-        f1.env_set(Environment())
-        try:
-            SCons.Node.FS.CacheRetrieve = retrieve_succeed
-            self.retrieved = []
-            built_it = None
-
-            r = f1.retrieve_from_cache()
-            assert r == 1, r
-            assert self.retrieved == [f1], self.retrieved
-            assert built_it is None, built_it
-
-            SCons.Node.FS.CacheRetrieve = retrieve_fail
-            self.retrieved = []
-            built_it = None
-
-            r = f1.retrieve_from_cache()
-            assert r is None, r
-            assert self.retrieved == [f1], self.retrieved
-            assert built_it is None, built_it
-        finally:
-            SCons.Node.FS.CacheRetrieve = save_CacheRetrieve
-
-        save_CacheRetrieveSilent = SCons.Node.FS.CacheRetrieveSilent
 
-        fs.cache_show = 1
 
-        f2 = fs.File("cd.f2")
-        f2.builder_set(Builder(fs.File))
-        f2.env_set(Environment())
-        try:
-            SCons.Node.FS.CacheRetrieveSilent = retrieve_succeed
-            self.retrieved = []
-            built_it = None
-
-            r = f2.retrieve_from_cache()
-            assert r == 1, r
-            assert self.retrieved == [f2], self.retrieved
-            assert built_it is None, built_it
-
-            SCons.Node.FS.CacheRetrieveSilent = retrieve_fail
-            self.retrieved = []
-            built_it = None
-
-            r = f2.retrieve_from_cache()
-            assert r is None, r
-            assert self.retrieved == [f2], self.retrieved
-            assert built_it is None, built_it
-        finally:
-            SCons.Node.FS.CacheRetrieveSilent = save_CacheRetrieveSilent
-
-        save_CachePush = SCons.Node.FS.CachePush
-        def push(target, source, env, self=self):
-            self.pushed.append(target)
-            return 0
-        SCons.Node.FS.CachePush = push
+class CacheDirTestCase(unittest.TestCase):
 
-        try:
-            self.pushed = []
-
-            cd_f3 = test.workpath("cd.f3")
-            f3 = fs.File(cd_f3)
-            f3.built()
-            assert self.pushed == [], self.pushed
-            test.write(cd_f3, "cd.f3\n")
-            f3.built()
-            assert self.pushed == [f3], self.pushed
-
-            self.pushed = []
-
-            cd_f4 = test.workpath("cd.f4")
-            f4 = fs.File(cd_f4)
-            f4.visited()
-            assert self.pushed == [], self.pushed
-            test.write(cd_f4, "cd.f4\n")
-            f4.visited()
-            assert self.pushed == [], self.pushed
-            fs.cache_force = 1
-            f4.visited()
-            assert self.pushed == [f4], self.pushed
-        finally:
-            SCons.Node.FS.CachePush = save_CachePush
-
-        # Verify how the cachepath() method determines the name
-        # of the file in cache.
-        def my_collect(list):
-            return list[0]
-        save_collect = SCons.Sig.MD5.collect
-        SCons.Sig.MD5.collect = my_collect
-        try:
-            f5 = fs.File("cd.f5")
-            f5.binfo = f5.new_binfo()
-            f5.binfo.ninfo.bsig = 'a_fake_bsig'
-            cp = f5.cachepath()
-            dirname = os.path.join('cache', 'A')
-            filename = os.path.join(dirname, 'a_fake_bsig')
-            assert cp == (dirname, filename), cp
-        finally:
-            SCons.Sig.MD5.collect = save_collect
+    def test_get_cachedir_csig(self):
+        fs = SCons.Node.FS.FS()
 
-        # Verify that no bsig raises an InternalERror
-        f6 = fs.File("cd.f6")
-        f6.binfo = f6.new_binfo()
-        exc_caught = 0
-        try:
-            cp = f6.cachepath()
-        except SCons.Errors.InternalError:
-            exc_caught = 1
-        assert exc_caught
-
-        # Verify that we raise a warning if we can't copy a file to cache.
-        save_copy2 = shutil.copy2
-        def copy2(src, dst):
-            raise OSError
-        shutil.copy2 = copy2
-        save_mkdir = os.mkdir
-        def mkdir(dir, mode=0):
-            pass
-        os.mkdir = mkdir
-        old_warn_exceptions = SCons.Warnings.warningAsException(1)
-        SCons.Warnings.enableWarningClass(SCons.Warnings.CacheWriteErrorWarning)
+        f9 = fs.File('f9')
+        r = f9.get_cachedir_csig()
+        assert r == 'd41d8cd98f00b204e9800998ecf8427e', r
 
-        try:
-            cd_f7 = test.workpath("cd.f7")
-            test.write(cd_f7, "cd.f7\n")
-            f7 = fs.File(cd_f7)
-            f7.binfo = f7.new_binfo()
-            f7.binfo.ninfo.bsig = 'f7_bsig'
 
-            warn_caught = 0
-            try:
-                f7.built()
-            except SCons.Warnings.CacheWriteErrorWarning:
-                warn_caught = 1
-            assert warn_caught
-        finally:
-            shutil.copy2 = save_copy2
-            os.mkdir = save_mkdir
-            SCons.Warnings.warningAsException(old_warn_exceptions)
-            SCons.Warnings.suppressWarningClass(SCons.Warnings.CacheWriteErrorWarning)
-
-        # Verify that we don't blow up if there's no strfunction()
-        # for an action.
-        act = Action()
-        act.strfunction = None
-        f8 = fs.File("cd.f8")
-        f8.builder_set(Builder(fs.File, action=act))
-        f8.env_set(Environment())
-        try:
-            SCons.Node.FS.CacheRetrieveSilent = retrieve_succeed
-            self.retrieved = []
-            built_it = None
-
-            r = f8.retrieve_from_cache()
-            assert r == 1, r
-            assert self.retrieved == [f8], self.retrieved
-            assert built_it is None, built_it
-
-            SCons.Node.FS.CacheRetrieveSilent = retrieve_fail
-            self.retrieved = []
-            built_it = None
-
-            r = f8.retrieve_from_cache()
-            assert r is None, r
-            assert self.retrieved == [f8], self.retrieved
-            assert built_it is None, built_it
-        finally:
-            SCons.Node.FS.CacheRetrieveSilent = save_CacheRetrieveSilent
 
 class clearTestCase(unittest.TestCase):
     def runTest(self):
@@ -2600,6 +3149,8 @@ class clearTestCase(unittest.TestCase):
         assert not f.rexists()
         assert str(f) == test.workpath('f'), str(f)
 
+
+
 class disambiguateTestCase(unittest.TestCase):
     def runTest(self):
         """Test calling the disambiguate() method."""
@@ -2626,6 +3177,41 @@ class disambiguateTestCase(unittest.TestCase):
         f = efile.disambiguate()
         assert f.__class__ is fff.__class__, f.__class__
 
+        test.subdir('build')
+        test.subdir(['build', 'bdir'])
+        test.write(['build', 'bfile'], "build/bfile\n")
+
+        test.subdir('src')
+        test.write(['src', 'bdir'], "src/bdir\n")
+        test.subdir(['src', 'bfile'])
+
+        test.subdir(['src', 'edir'])
+        test.write(['src', 'efile'], "src/efile\n")
+
+        fs.VariantDir(test.workpath('build'), test.workpath('src'))
+
+        build_bdir = fs.Entry(test.workpath('build/bdir'))
+        d = build_bdir.disambiguate()
+        assert d is build_bdir, d
+        assert d.__class__ is ddd.__class__, d.__class__
+
+        build_bfile = fs.Entry(test.workpath('build/bfile'))
+        f = build_bfile.disambiguate()
+        assert f is build_bfile, f
+        assert f.__class__ is fff.__class__, f.__class__
+
+        build_edir = fs.Entry(test.workpath('build/edir'))
+        d = build_edir.disambiguate()
+        assert d.__class__ is ddd.__class__, d.__class__
+
+        build_efile = fs.Entry(test.workpath('build/efile'))
+        f = build_efile.disambiguate()
+        assert f.__class__ is fff.__class__, f.__class__
+
+        build_nonexistant = fs.Entry(test.workpath('build/nonexistant'))
+        f = build_nonexistant.disambiguate()
+        assert f.__class__ is fff.__class__, f.__class__
+
 class postprocessTestCase(unittest.TestCase):
     def runTest(self):
         """Test calling the postprocess() method."""
@@ -2640,6 +3226,8 @@ class postprocessTestCase(unittest.TestCase):
         f = fs.File('f')
         f.postprocess()
 
+
+
 class SpecialAttrTestCase(unittest.TestCase):
     def runTest(self):
         """Test special attributes of file nodes."""
@@ -2691,12 +3279,20 @@ class SpecialAttrTestCase(unittest.TestCase):
             for_sig = f.posix.for_signature()
             assert for_sig == 'baz.blat_posix', for_sig
 
+        s = str(f.windows)
+        assert s == 'foo\\bar\\baz.blat', repr(s)
+        assert f.windows.is_literal(), f.windows
+        if f.windows != f:
+            for_sig = f.windows.for_signature()
+            assert for_sig == 'baz.blat_windows', for_sig
+
+        # Deprecated synonym for the .windows suffix.
         s = str(f.win32)
         assert s == 'foo\\bar\\baz.blat', repr(s)
         assert f.win32.is_literal(), f.win32
         if f.win32 != f:
             for_sig = f.win32.for_signature()
-            assert for_sig == 'baz.blat_win32', for_sig
+            assert for_sig == 'baz.blat_windows', for_sig
 
         # And now, combinations!!!
         s = str(f.srcpath.base)
@@ -2705,17 +3301,19 @@ class SpecialAttrTestCase(unittest.TestCase):
         assert s == str(f.srcdir), s
         s = str(f.srcpath.posix)
         assert s == 'foo/bar/baz.blat', s
+        s = str(f.srcpath.windows)
+        assert s == 'foo\\bar\\baz.blat', s
         s = str(f.srcpath.win32)
         assert s == 'foo\\bar\\baz.blat', s
 
-        # Test what happens with BuildDir()
-        fs.BuildDir('foo', 'baz')
+        # Test what happens with VariantDir()
+        fs.VariantDir('foo', 'baz')
 
         s = str(f.srcpath)
         assert s == os.path.normpath('baz/bar/baz.blat'), s
         assert f.srcpath.is_literal(), f.srcpath
         g = f.srcpath.get()
-        assert isinstance(g, SCons.Node.FS.Entry), g.__class__
+        assert isinstance(g, SCons.Node.FS.File), g.__class__
 
         s = str(f.srcdir)
         assert s == os.path.normpath('baz/bar'), s
@@ -2723,7 +3321,7 @@ class SpecialAttrTestCase(unittest.TestCase):
         g = f.srcdir.get()
         assert isinstance(g, SCons.Node.FS.Dir), g.__class__
 
-        # And now what happens with BuildDir() + Repository()
+        # And now what happens with VariantDir() + Repository()
         fs.Repository(test.workpath('repository'))
 
         f = fs.Entry('foo/sub/file.suffix').get_subst_proxy()
@@ -2739,7 +3337,8 @@ class SpecialAttrTestCase(unittest.TestCase):
         assert s == os.path.normpath('baz/sub/file.suffix'), s
         assert f.srcpath.is_literal(), f.srcpath
         g = f.srcpath.get()
-        assert isinstance(g, SCons.Node.FS.Entry), g.__class__
+        # Gets disambiguated to SCons.Node.FS.File by get_subst_proxy().
+        assert isinstance(g, SCons.Node.FS.File), g.__class__
 
         s = str(f.srcdir)
         assert s == os.path.normpath('baz/sub'), s
@@ -2773,7 +3372,8 @@ class SpecialAttrTestCase(unittest.TestCase):
         try:
             fs.Entry('eee').get_subst_proxy().no_such_attr
         except AttributeError, e:
-            assert str(e) == "Entry instance 'eee' has no attribute 'no_such_attr'", e
+            # Gets disambiguated to File instance by get_subst_proxy().
+            assert str(e) == "File instance 'eee' has no attribute 'no_such_attr'", e
             caught = 1
         assert caught, "did not catch expected AttributeError"
 
@@ -2785,6 +3385,8 @@ class SpecialAttrTestCase(unittest.TestCase):
             caught = 1
         assert caught, "did not catch expected AttributeError"
 
+
+
 class SaveStringsTestCase(unittest.TestCase):
     def runTest(self):
         """Test caching string values of nodes."""
@@ -2815,62 +3417,91 @@ class SaveStringsTestCase(unittest.TestCase):
 
         fs1 = SCons.Node.FS.FS(test.workpath('fs1'))
         nodes = setup(fs1)
-        fs1.BuildDir('d0', 'src', duplicate=0)
-        fs1.BuildDir('d1', 'src', duplicate=1)
+        fs1.VariantDir('d0', 'src', duplicate=0)
+        fs1.VariantDir('d1', 'src', duplicate=1)
 
-        s = map(str, nodes)
-        expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b'])
+        s = list(map(str, nodes))
+        expect = list(map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']))
         assert s == expect, s
 
         modify(nodes)
 
-        s = map(str, nodes)
-        expect = map(os.path.normpath, ['src/f', 'src/f', 'd0/b', 'd1/b'])
+        s = list(map(str, nodes))
+        expect = list(map(os.path.normpath, ['src/f', 'src/f', 'd0/b', 'd1/b']))
         assert s == expect, s
 
         SCons.Node.FS.save_strings(1)
         fs2 = SCons.Node.FS.FS(test.workpath('fs2'))
         nodes = setup(fs2)
-        fs2.BuildDir('d0', 'src', duplicate=0)
-        fs2.BuildDir('d1', 'src', duplicate=1)
+        fs2.VariantDir('d0', 'src', duplicate=0)
+        fs2.VariantDir('d1', 'src', duplicate=1)
 
-        s = map(str, nodes)
-        expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b'])
+        s = list(map(str, nodes))
+        expect = list(map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']))
         assert s == expect, s
 
         modify(nodes)
 
-        s = map(str, nodes)
-        expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b'])
+        s = list(map(str, nodes))
+        expect = list(map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']))
         assert s == expect, 'node str() not cached: %s'%s
 
+
+class AbsolutePathTestCase(unittest.TestCase):
+    def test_root_lookup_equivalence(self):
+        """Test looking up /fff vs. fff in the / directory"""
+        test=TestCmd(workdir='')
+
+        fs = SCons.Node.FS.FS('/')
+
+        save_cwd = os.getcwd()
+        try:
+            os.chdir('/')
+            fff1 = fs.File('fff')
+            fff2 = fs.File('/fff')
+            assert fff1 is fff2, "fff and /fff returned different Nodes!"
+        finally:
+            os.chdir(save_cwd)
+
+
+
 if __name__ == "__main__":
     suite = unittest.TestSuite()
-    suite.addTest(BuildDirTestCase())
+    suite.addTest(VariantDirTestCase())
     suite.addTest(find_fileTestCase())
     suite.addTest(StringDirTestCase())
     suite.addTest(stored_infoTestCase())
     suite.addTest(has_src_builderTestCase())
     suite.addTest(prepareTestCase())
     suite.addTest(SConstruct_dirTestCase())
-    suite.addTest(CacheDirTestCase())
     suite.addTest(clearTestCase())
     suite.addTest(disambiguateTestCase())
     suite.addTest(postprocessTestCase())
     suite.addTest(SpecialAttrTestCase())
     suite.addTest(SaveStringsTestCase())
     tclasses = [
+        AbsolutePathTestCase,
         BaseTestCase,
-        BuildInfoTestCase,
+        CacheDirTestCase,
+        DirTestCase,
+        DirBuildInfoTestCase,
+        DirNodeInfoTestCase,
         EntryTestCase,
         FileTestCase,
-        NodeInfoTestCase,
+        FileBuildInfoTestCase,
+        FileNodeInfoTestCase,
         FSTestCase,
-        DirTestCase,
+        GlobTestCase,
         RepositoryTestCase,
     ]
     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: