text: # MIT License
  #
  # Copyright The SCons Foundation
  #
  # Permission is hereby granted, free of charge, to any person obtaining
  # a copy of this software and associated documentation files (the
  # "Software"), to deal in the Software without restriction, including
  # without limitation the rights to use, copy, modify, merge, publish,
  # distribute, sublicense, and/or sell copies of the Software, and to
  # permit persons to whom the Software is furnished to do so, subject to
  # the following conditions:
  #
  # The above copyright notice and this permission notice shall be included
  # in all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  import SCons.compat
  import os
  import os.path
  import sys
  import time
  import unittest
  import shutil
  import stat
  
  from TestCmd import TestCmd, IS_WINDOWS
  import TestUnit
  
  import SCons.Errors
  import SCons.Node.FS
  import SCons.Util
  import SCons.Warnings
  import SCons.Environment
  
  built_it = None
  
  scanner_count = 0
  
  
  class Scanner:
      def __init__(self, node=None):
          global scanner_count
          scanner_count = scanner_count + 1
          self.hash = scanner_count
          self.node = node
  
      def path(self, env, dir, target=None, source=None):
          return ()
  
      def __call__(self, node, env, path):
          return [self.node]
  
      def __hash__(self):
          return self.hash
  
      def select(self, node):
          return self
  
      def recurse_nodes(self, nodes):
          return nodes
  
  
  class Environment:
      def __init__(self):
          self.scanner = Scanner()
  
      def Dictionary(self, *args):
          return {}
  
      def autogenerate(self, **kw):
          return {}
  
      def get_scanner(self, skey):
          return self.scanner
  
      def Override(self, overrides):
          return self
  
      def _update(self, dict):
          pass
  
  
  class Action:
      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 bytearray("", 'utf-8')
  
      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
          self.env = Environment()
          self.overrides = {}
          self.action = action
          self.target_scanner = None
          self.source_scanner = None
  
      def targets(self, t):
          return [t]
  
      def source_factory(self, name):
          return self.factory(name)
  
  
  class _tempdirTestCase(unittest.TestCase):
      def setUp(self):
          self.save_cwd = os.getcwd()
          self.test = TestCmd(workdir='')
          # FS doesn't like the cwd to be something other than its root.
          os.chdir(self.test.workpath(""))
          self.fs = SCons.Node.FS.FS()
  
      def tearDown(self):
          os.chdir(self.save_cwd)
  
  
  class VariantDirTestCase(unittest.TestCase):
      def runTest(self):
          """Test variant dir functionality"""
          test = TestCmd(workdir='')
  
          fs = SCons.Node.FS.FS()
          f1 = fs.File('build/test1')
          fs.VariantDir('build', 'src')
          f2 = fs.File('build/test2')
          d1 = fs.Dir('build')
          assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path()
          assert f2.srcnode().get_internal_path() == os.path.normpath('src/test2'), f2.srcnode().get_internal_path()
          assert d1.srcnode().get_internal_path() == 'src', d1.srcnode().get_internal_path()
  
          fs = SCons.Node.FS.FS()
          f1 = fs.File('build/test1')
          fs.VariantDir('build', '.')
          f2 = fs.File('build/test2')
          d1 = fs.Dir('build')
          assert f1.srcnode().get_internal_path() == 'test1', f1.srcnode().get_internal_path()
          assert f2.srcnode().get_internal_path() == 'test2', f2.srcnode().get_internal_path()
          assert d1.srcnode().get_internal_path() == '.', d1.srcnode().get_internal_path()
  
          fs = SCons.Node.FS.FS()
          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().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path()
          assert f2.srcnode().get_internal_path() == os.path.normpath('src/test1'), f2.srcnode().get_internal_path()
  
          fs = SCons.Node.FS.FS()
          fs.VariantDir('../var1', 'src')
          fs.VariantDir('../var2', 'src')
          f1 = fs.File('../var1/test1')
          f2 = fs.File('../var2/test1')
          assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path()
          assert f2.srcnode().get_internal_path() == os.path.normpath('src/test1'), f2.srcnode().get_internal_path()
  
          # Set up some files
          test.subdir('work', ['work', 'src'])
          test.subdir(['work', 'build'], ['work', 'build', 'var1'])
          test.subdir(['work', 'build', 'var2'])
          test.subdir('rep1', ['rep1', 'src'])
          test.subdir(['rep1', 'build'], ['rep1', 'build', 'var1'])
          test.subdir(['rep1', 'build', 'var2'])
  
          # A source file in the source directory
          test.write(['work', 'src', 'test.in'], 'test.in')
  
          # A source file in a subdir of the source directory
          test.subdir(['work', 'src', 'new_dir'])
          test.write(['work', 'src', 'new_dir', 'test9.out'], 'test9.out\n')
  
          # A source file in the repository
          test.write(['rep1', 'src', 'test2.in'], 'test2.in')
  
          # 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 variant directories
          test.write(['work', 'build', 'var1', 'test.out'], 'test.old')
          test.write(['work', 'build', 'var2', 'test.out'], 'test.old')
  
          # And just in case we are weird, a derived file in the source
          # dir.
          test.write(['work', 'src', 'test.out'], 'test.out.src')
  
          # A derived file in the repository
          test.write(['rep1', 'build', 'var1', 'test2.out'], 'test2.out_rep')
          test.write(['rep1', 'build', 'var2', 'test2.out'], 'test2.out_rep')
  
          os.chdir(test.workpath('work'))
  
          fs = SCons.Node.FS.FS(test.workpath('work'))
          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
          f1out_2 = fs.File('build/var1/test2.out')
          f1out_2.builder = 1
          f2 = fs.File('build/var2/test.in')
          f2out = fs.File('build/var2/test.out')
          f2out.builder = 1
          f2out_2 = fs.File('build/var2/test2.out')
          f2out_2.builder = 1
          fs.Repository(test.workpath('rep1'))
  
          assert f1.srcnode().get_internal_path() == os.path.normpath('src/test.in'), \
              f1.srcnode().get_internal_path()
          # str(node) returns source path for duplicate = 0
          assert str(f1) == os.path.normpath('src/test.in'), str(f1)
          # Build path does not exist
          assert not f1.exists()
          # ...but the actual file is not there...
          assert not os.path.exists(f1.get_abspath())
          # And duplicate=0 should also work just like a Repository
          assert f1.rexists()
          # rfile() should point to the source path
          assert f1.rfile().get_internal_path() == os.path.normpath('src/test.in'), \
              f1.rfile().get_internal_path()
  
          assert f2.srcnode().get_internal_path() == os.path.normpath('src/test.in'), \
              f2.srcnode().get_internal_path()
          # str(node) returns build path for duplicate = 1
          assert str(f2) == os.path.normpath('build/var2/test.in'), str(f2)
          # Build path exists
          assert f2.exists()
          # ...and exists() should copy the file from src to build path
          assert test.read(['work', 'build', 'var2', 'test.in']) == bytearray('test.in', 'utf-8'), \
              test.read(['work', 'build', 'var2', 'test.in'])
          # Since exists() is true, so should rexists() be
          assert f2.rexists()
  
          f3 = fs.File('build/var1/test2.in')
          f4 = fs.File('build/var2/test2.in')
  
          assert f3.srcnode().get_internal_path() == os.path.normpath('src/test2.in'), \
              f3.srcnode().get_internal_path()
          # str(node) returns source path for duplicate = 0
          assert str(f3) == os.path.normpath('src/test2.in'), str(f3)
          # Build path does not exist
          assert not f3.exists()
          # Source path does not either
          assert not f3.srcnode().exists()
          # But we do have a file in the Repository
          assert f3.rexists()
          # rfile() should point to the source path
          assert f3.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/src/test2.in')), \
              f3.rfile().get_internal_path()
  
          assert f4.srcnode().get_internal_path() == os.path.normpath('src/test2.in'), \
              f4.srcnode().get_internal_path()
          # str(node) returns build path for duplicate = 1
          assert str(f4) == os.path.normpath('build/var2/test2.in'), str(f4)
          # Build path should exist
          assert f4.exists()
          # ...and copy over the file into the local build path
          assert test.read(['work', 'build', 'var2', 'test2.in']) == bytearray('test2.in', 'utf-8')
          # should exist in repository, since exists() is true
          assert f4.rexists()
          # rfile() should point to ourselves
          assert f4.rfile().get_internal_path() == os.path.normpath('build/var2/test2.in'), \
              f4.rfile().get_internal_path()
  
          f5 = fs.File('build/var1/test.out')
          f6 = fs.File('build/var2/test.out')
  
          assert f5.exists()
          # We should not copy the file from the source dir, since this is
          # a derived file.
          assert test.read(['work', 'build', 'var1', 'test.out']) == bytearray('test.old', 'utf-8')
  
          assert f6.exists()
          # We should not copy the file from the source dir, since this is
          # a derived file.
          assert test.read(['work', 'build', 'var2', 'test.out']) == bytearray('test.old', 'utf-8')
  
          f7 = fs.File('build/var1/test2.out')
          f8 = fs.File('build/var2/test2.out')
  
          assert not f7.exists()
          assert f7.rexists()
          r = f7.rfile().get_internal_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()
          assert f8.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/build/var2/test2.out')), \
              f8.rfile().get_internal_path()
  
          # Verify the Mkdir and Link actions are called
          d9 = fs.Dir('build/var2/new_dir')
          f9 = fs.File('build/var2/new_dir/test9.out')
  
          class MkdirAction(Action):
              def __init__(self, dir_made):
                  self.dir_made = dir_made
  
              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
          link_made = []
  
          def link_func(target, source, env, link_made=link_made):
              link_made.append(target)
  
          SCons.Node.FS.Link = link_func
  
          try:
              dir_made = []
              d9.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
              d9.reset_executor()
              f9.exists()
              expect = os.path.join('build', 'var2', 'new_dir')
              assert dir_made[0].get_internal_path() == expect, dir_made[0].get_internal_path()
              expect = os.path.join('build', 'var2', 'new_dir', 'test9.out')
              assert link_made[0].get_internal_path() == expect, link_made[0].get_internal_path()
              assert f9.linked
          finally:
              SCons.Node.FS.Link = save_Link
  
          # Test for an interesting pathological case...we have a source
          # file in a build path, but not in a source path.  This can
          # happen if you switch from duplicate=1 to duplicate=0, then
          # delete a source file.  At one time, this would cause exists()
          # to return a 1 but get_contents() to throw.
          test.write(['work', 'build', 'var1', 'asourcefile'], 'stuff')
          f10 = fs.File('build/var1/asourcefile')
          assert f10.exists()
          assert f10.get_contents() == bytearray('stuff', 'utf-8'), f10.get_contents()
  
          f11 = fs.File('src/file11')
          t, m = f11.alter_targets()
          bdt = [n.get_internal_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
  
          f12 = fs.File('src/file12')
          f12.builder = 1
          bdt, m = f12.alter_targets()
          assert bdt == [], [n.get_internal_path() for n in bdt]
  
          d13 = fs.Dir('src/new_dir')
          t, m = d13.alter_targets()
          bdt = [n.get_internal_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 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(17, "Link_IOError")
  
          SCons.Node.FS.Link = SCons.Action.Action(Link_IOError, None)
  
          test.write(['work', 'src', 'IOError'], "work/src/IOError\n")
  
          try:
              exc_caught = 0
              try:
                  fIO.exists()
              except SCons.Errors.StopError:
                  exc_caught = 1
              assert exc_caught, "Should have caught a StopError"
  
          finally:
              SCons.Node.FS.Link = save_Link
  
          # Test to see if Link() works...
          test.subdir('src', 'build')
          test.write('src/foo', 'src/foo\n')
          os.chmod(test.workpath('src/foo'), stat.S_IRUSR)
          SCons.Node.FS.Link(fs.File(test.workpath('build/foo')),
                             fs.File(test.workpath('src/foo')),
                             None)
          os.chmod(test.workpath('src/foo'), stat.S_IRUSR | stat.S_IWRITE)
          st = os.stat(test.workpath('build/foo'))
          assert (stat.S_IMODE(st[stat.ST_MODE]) & stat.S_IWRITE), \
              stat.S_IMODE(st[stat.ST_MODE])
  
          # 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.VariantDir('build', '/test/foo')
  
          exc_caught = 0
          try:
              try:
                  fs = SCons.Node.FS.FS()
                  fs.VariantDir('build', 'build/src')
              except SCons.Errors.UserError:
                  exc_caught = 1
              assert exc_caught, "Should have caught a UserError."
          finally:
              test.unlink("src/foo")
              test.unlink("build/foo")
  
          fs = SCons.Node.FS.FS()
          fs.VariantDir('build', 'src1')
  
          # Calling the same VariantDir twice should work fine.
          fs.VariantDir('build', 'src1')
  
          # Trying to move a variant dir to a second source dir
          # should blow up
          try:
              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 variant directory itself!
          fs = SCons.Node.FS.FS(test.workpath('work'))
  
          # not needed this subdir is created above near line 188
          # test.subdir('work')
          fs.VariantDir('build/var3', 'src', duplicate=0)
          d1 = fs.Dir('build/var3')
          r = d1.rdir()
          assert r == d1, "%s != %s" % (r, d1)
  
          # verify the link creation attempts in file_link()
          class LinkSimulator:
              """A class to intercept os.[sym]link() calls and track them."""
  
              def __init__(self, duplicate, link, symlink, copy):
                  self.duplicate = duplicate
                  self.have = {'hard': link, 'soft': symlink, '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):
                  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
                  raise OSError("Simulating hard link creation error.")
  
              def symlink_fail(self, src, dest):
                  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
                  raise OSError("Simulating symlink creation error.")
  
              def copy(self, src, dest):
                  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
                  # copy succeeds, but use the real copy
                  self.have['copy'](src, dest)
  
          # end class LinkSimulator
  
          try:
              SCons.Node.FS.set_duplicate("no-link-order")
              assert 0, "Expected exception when passing an invalid duplicate to set_duplicate"
          except SCons.Errors.InternalError:
              pass
  
          for duplicate in SCons.Node.FS.Valid_Duplicates:
              # save the real functions for later restoration
              try:
                  real_link = os.link
              except AttributeError:
                  real_link = None
              try:
                  real_symlink = os.symlink
              except AttributeError:
                  real_symlink = None
  
              # Disable symlink and link for now in win32.
              # We don't have a consistant plan to make these work as yet
              # They are only supported with PY3
              if sys.platform == 'win32':
                  real_symlink = None
                  real_link = None
  
              real_copy = shutil.copy2
  
              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
  
              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:
                      SCons.Node.FS.Link(fs.File(build_foo),
                                         fs.File(src_foo),
                                         None)
                  finally:
                      os.chmod(src_foo, stat.S_IRUSR | stat.S_IWRITE)
                      test.unlink(src_foo)
                      test.unlink(build_foo)
  
              finally:
                  # restore the real functions
                  if real_link:
                      os.link = real_link
                  else:
                      delattr(os, 'link')
                  if real_symlink:
                      os.symlink = real_symlink
                  else:
                      delattr(os, 'symlink')
                  shutil.copy2 = real_copy
  
          # Test VariantDir "reflection," where a same-named subdirectory
          # exists underneath a variant_dir.
          fs = SCons.Node.FS.FS()
          fs.VariantDir('work/src/b1/b2', 'work/src')
  
          dir_list = [
              'work/src',
              'work/src/b1',
              '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 = {
              'work/src/b1/b2': 'work/src',
              'work/src/b1/b2/f': 'work/src/f',
              'work/src/b1/b2/b1': 'work/src/b1/',
              '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 = {
              'work/src': 'work/src/b1/b2',
              'work/src/f': 'work/src/b1/b2/f',
              'work/src/b1': 'work/src/b1/b2/b1',
              'work/src/b1/f': 'work/src/b1/b2/b1/f',
          }
  
          errors = 0
  
          for dir in dir_list:
              dnode = fs.Dir(dir)
              f = dir + '/f'
              fnode = fs.File(dir + '/f')
  
              dp = dnode.srcnode().get_internal_path()
              expect = os.path.normpath(srcnode_map.get(dir, dir))
              if dp != expect:
                  print("Dir `%s' srcnode() `%s' != expected `%s'" % (dir, dp, expect))
                  errors = errors + 1
  
              fp = fnode.srcnode().get_internal_path()
              expect = os.path.normpath(srcnode_map.get(f, f))
              if fp != expect:
                  print("File `%s' srcnode() `%s' != expected `%s'" % (f, fp, expect))
                  errors = errors + 1
  
          for dir in dir_list:
              dnode = fs.Dir(dir)
              f = dir + '/f'
              fnode = fs.File(dir + '/f')
  
              t, m = dnode.alter_targets()
              tp = t[0].get_internal_path()
              expect = os.path.normpath(alter_map.get(dir, dir))
              if tp != expect:
                  print("Dir `%s' alter_targets() `%s' != expected `%s'" % (dir, tp, expect))
                  errors = errors + 1
  
              t, m = fnode.alter_targets()
              tp = t[0].get_internal_path()
              expect = os.path.normpath(alter_map.get(f, f))
              if tp != expect:
                  print("File `%s' alter_targets() `%s' != expected `%s'" % (f, tp, expect))
                  errors = errors + 1
  
          self.assertFalse(errors)
  
  
  class BaseTestCase(_tempdirTestCase):
      def test_stat(self):
          """Test the Base.stat() method"""
          test = self.test
          test.write("e1", "e1\n")
          fs = SCons.Node.FS.FS()
  
          e1 = fs.Entry('e1')
          s = e1.stat()
          assert s is not None, s
  
          e2 = fs.Entry('e2')
          s = e2.stat()
          assert s is None, s
  
      def test_getmtime(self):
          """Test the Base.getmtime() method"""
          test = self.test
          test.write("file", "file\n")
          fs = SCons.Node.FS.FS()
  
          file = fs.Entry('file')
          assert file.getmtime()
  
          file = fs.Entry('nonexistent')
          mtime = file.getmtime()
          assert mtime is None, mtime
  
      def test_getsize(self):
          """Test the Base.getsize() method"""
          test = self.test
          test.write("file", "file\n")
          fs = SCons.Node.FS.FS()
  
          file = fs.Entry('file')
          size = file.getsize()
          assert size == 5, size
  
          file = fs.Entry('nonexistent')
          size = file.getsize()
          assert size is None, size
  
      def test_isdir(self):
          """Test the Base.isdir() method"""
          test = self.test
          test.subdir('dir')
          test.write("file", "file\n")
          fs = SCons.Node.FS.FS()
  
          dir = fs.Entry('dir')
          assert dir.isdir()
  
          file = fs.Entry('file')
          assert not file.isdir()
  
          nonexistent = fs.Entry('nonexistent')
          assert not nonexistent.isdir()
  
      def test_isfile(self):
          """Test the Base.isfile() method"""
          test = self.test
          test.subdir('dir')
          test.write("file", "file\n")
          fs = SCons.Node.FS.FS()
  
          dir = fs.Entry('dir')
          assert not dir.isfile()
  
          file = fs.Entry('file')
          assert file.isfile()
  
          nonexistent = fs.Entry('nonexistent')
          assert not nonexistent.isfile()
  
      @unittest.skipUnless(sys.platform != 'win32' and hasattr(os, 'symlink'),
                           "symlink is not used on Windows")
      def test_islink(self):
          """Test the Base.islink() method"""
          test = self.test
          test.subdir('dir')
          test.write("file", "file\n")
          test.symlink("symlink", "symlink")
          fs = SCons.Node.FS.FS()
  
          dir = fs.Entry('dir')
          assert not dir.islink()
  
          file = fs.Entry('file')
          assert not file.islink()
  
          symlink = fs.Entry('symlink')
          assert symlink.islink()
  
          nonexistent = fs.Entry('nonexistent')
          assert not nonexistent.islink()
  
  
  class DirNodeInfoTestCase(_tempdirTestCase):
      def test___init__(self):
          """Test DirNodeInfo initialization"""
          ddd = self.fs.Dir('ddd')
          ni = SCons.Node.FS.DirNodeInfo()
  
  
  class DirBuildInfoTestCase(_tempdirTestCase):
      def test___init__(self):
          """Test DirBuildInfo initialization"""
          ddd = self.fs.Dir('ddd')
          bi = SCons.Node.FS.DirBuildInfo()
  
  
  class FileNodeInfoTestCase(_tempdirTestCase):
      def test___init__(self):
          """Test FileNodeInfo initialization"""
          fff = self.fs.File('fff')
          ni = SCons.Node.FS.FileNodeInfo()
          assert isinstance(ni, SCons.Node.FS.FileNodeInfo)
  
      def test_update(self):
          """Test updating a File.NodeInfo with on-disk information"""
          test = self.test
          fff = self.fs.File('fff')
  
          ni = SCons.Node.FS.FileNodeInfo()
  
          test.write('fff', "fff\n")
  
          st = os.stat('fff')
  
          ni.update(fff)
  
          assert hasattr(ni, 'timestamp')
          assert hasattr(ni, 'size')
  
          ni.timestamp = 0
          ni.size = 0
  
          ni.update(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)
  
          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)
  
  
  class FileBuildInfoTestCase(_tempdirTestCase):
      def test___init__(self):
          """Test File.BuildInfo initialization"""
          fff = self.fs.File('fff')
          bi = SCons.Node.FS.FileBuildInfo()
          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()
          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()
          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()
          bi.prepare_dependencies()
  
      def test_format(self):
          """Test the format() method"""
          f1 = self.fs.File('f1')
          bi1 = SCons.Node.FS.FileBuildInfo()
  
          self.fs.File('n1')
          self.fs.File('n2')
          self.fs.File('n3')
  
          s1sig = SCons.Node.FS.FileNodeInfo()
          s1sig.csig = 1
          d1sig = SCons.Node.FS.FileNodeInfo()
          d1sig.timestamp = 2
          i1sig = SCons.Node.FS.FileNodeInfo()
          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_needs_normpath(self):
          """Test the needs_normpath Regular expression
  
          This test case verifies that the regular expression used to
          determine whether a path needs normalization works as
          expected.
          """
          needs_normpath_match = SCons.Node.FS.needs_normpath_match
  
          do_not_need_normpath = [
              ".",
              "/",
              "/a",
              "/aa",
              "/a/",
              "/aa/",
              "/a/b",
              "/aa/bb",
              "/a/b/",
              "/aa/bb/",
  
              "",
              "a",
              "aa",
              "a/",
              "aa/",
              "a/b",
              "aa/bb",
              "a/b/",
              "aa/bb/",
  
              "a.",
              "a..",
              "/a.",
              "/a..",
              "a./",
              "a../",
              "/a./",
              "/a../",
  
              ".a",
              "..a",
              "/.a",
              "/..a",
              ".a/",
              "..a/",
              "/.a/",
              "/..a/",
          ]
          for p in do_not_need_normpath:
              assert needs_normpath_match(p) is None, p
  
          needs_normpath = [
              "//",
              "//a",
              "//aa",
              "//a/",
              "//a/",
              "/aa//",
  
              "//a/b",
              "//aa/bb",
              "//a/b/",
              "//aa/bb/",
  
              "/a//b",
              "/aa//bb",
              "/a/b//",
              "/aa/bb//",
  
              "/a/b//",
              "/aa/bb//",
  
              "a//",
              "aa//",
              "a//b",
              "aa//bb",
              "a//b/",
              "aa//bb/",
              "a/b//",
              "aa/bb//",
  
              "..",
              "/.",
              "/..",
              "./",
              "../",
              "/./",
              "/../",
  
              "a/.",
              "a/..",
              "./a",
              "../a",
              "a/./a",
              "a/../a",
          ]
          for p in needs_normpath:
              assert needs_normpath_match(p) is not None, p
  
      def test_runTest(self):
          """Test FS (file system) Node operations
  
          This test case handles all of the file system node
          tests in one environment, so we don't have to set up a
          complicated directory structure for each test individually.
          """
          test = self.test
  
          test.subdir('sub', ['sub', 'dir'])
  
          wp = test.workpath('')
          sub = test.workpath('sub', '')
          sub_dir = test.workpath('sub', 'dir', '')
          sub_dir_foo = test.workpath('sub', 'dir', 'foo', '')
          sub_dir_foo_bar = test.workpath('sub', 'dir', 'foo', 'bar', '')
          sub_foo = test.workpath('sub', 'foo', '')
  
          os.chdir(sub_dir)
  
          fs = SCons.Node.FS.FS()
  
          e1 = fs.Entry('e1')
          assert isinstance(e1, SCons.Node.FS.Entry)
  
          d1 = fs.Dir('d1')
          assert isinstance(d1, SCons.Node.FS.Dir)
          assert d1.cwd is d1, d1
  
          f1 = fs.File('f1', directory=d1)
          assert isinstance(f1, SCons.Node.FS.File)
  
          d1_f1 = os.path.join('d1', 'f1')
          assert f1.get_internal_path() == d1_f1, "f1.path %s != %s" % (f1.get_internal_path(), d1_f1)
          assert str(f1) == d1_f1, "str(f1) %s != %s" % (str(f1), d1_f1)
  
          x1 = d1.File('x1')
          assert isinstance(x1, SCons.Node.FS.File)
          assert str(x1) == os.path.join('d1', 'x1')
  
          x2 = d1.Dir('x2')
          assert isinstance(x2, SCons.Node.FS.Dir)
          assert str(x2) == os.path.join('d1', 'x2')
  
          x3 = d1.Entry('x3')
          assert isinstance(x3, SCons.Node.FS.Entry)
          assert str(x3) == os.path.join('d1', 'x3')
  
          assert d1.File(x1) == x1
          assert d1.Dir(x2) == x2
          assert d1.Entry(x3) == x3
  
          x1.cwd = d1
  
          x4 = x1.File('x4')
          assert str(x4) == os.path.join('d1', 'x4')
  
          x5 = x1.Dir('x5')
          assert str(x5) == os.path.join('d1', 'x5')
  
          x6 = x1.Entry('x6')
          assert str(x6) == os.path.join('d1', 'x6')
          x7 = x1.Entry('x7')
          assert str(x7) == os.path.join('d1', 'x7')
  
          assert x1.File(x4) == x4
          assert x1.Dir(x5) == x5
          assert x1.Entry(x6) == x6
          assert x1.Entry(x7) == x7
  
          assert x1.Entry(x5) == x5
          try:
              x1.File(x5)
          except TypeError:
              pass
          else:
              raise Exception("did not catch expected TypeError")
  
          assert x1.Entry(x4) == x4
          try:
              x1.Dir(x4)
          except TypeError:
              pass
          else:
              raise Exception("did not catch expected TypeError")
  
          x6 = x1.File(x6)
          assert isinstance(x6, SCons.Node.FS.File)
  
          x7 = x1.Dir(x7)
          assert isinstance(x7, SCons.Node.FS.Dir)
  
          seps = [os.sep]
          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]
  
              if not name:
                  if drive:
                      name = drive
                  else:
                      name = os.sep
  
              if dir.up() is None:
                  dir_up_path = dir.get_internal_path()
              else:
                  dir_up_path = dir.up().get_internal_path()
  
              assert dir.name == name, \
                  "dir.name %s != expected name %s" % \
                  (dir.name, name)
              assert dir.get_internal_path() == path, \
                  "dir.path %s != expected path %s" % \
                  (dir.get_internal_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_, sep=sep, func=_do_Dir_test):
                  return func(lpath, path_, abspath_, up_path_, sep)
  
              Dir_test('/', '/', '/', '/')
              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/', '/')
              Dir_test('/foo/bar', '/foo/bar/', '/foo/bar/', '/foo/')
              Dir_test('..', sub, sub, wp)
              Dir_test('foo/..', './', sub_dir, sub)
              Dir_test('../foo', sub_foo, sub_foo, sub)
              Dir_test('.', './', sub_dir, sub)
              Dir_test('./.', './', sub_dir, sub)
              Dir_test('foo/./bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
              Dir_test('#../foo', sub_foo, sub_foo, sub)
              Dir_test('#/../foo', sub_foo, sub_foo, sub)
              Dir_test('#foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
              Dir_test('#/foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
              Dir_test('#', './', sub_dir, sub)
  
              try:
                  f2 = fs.File(sep.join(['f1', 'f2']), directory=d1)
              except TypeError as x:
                  assert str(x) == ("Tried to lookup File '%s' as a Dir." %
                                    d1_f1), x
              except:
                  raise
  
              try:
                  dir = fs.Dir(sep.join(['d1', 'f1']))
              except TypeError as x:
                  assert str(x) == ("Tried to lookup File '%s' as a Dir." %
                                    d1_f1), x
              except:
                  raise
  
              try:
                  f2 = fs.File('d1')
              except TypeError as x:
                  assert str(x) == ("Tried to lookup Dir '%s' as a File." %
                                    'd1'), x
              except:
                  raise
  
          # 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)
  
              e1 = fs.Entry(p)
              e2 = fs.Entry(path)
              assert e1 is e2, (e1, e2)
              a = str(e1)
              b = str(e2)
              assert a == b, ("Strings should match for same file/node\n%s\n%s" % (a, b))
  
          # Test for a bug in 0.04 that did not like looking up
          # dirs with a trailing slash on Windows.
          d = fs.Dir('./')
          assert d.get_internal_path() == '.', d.get_abspath()
          d = fs.Dir('foo/')
          assert d.get_internal_path() == 'foo', d.get_abspath()
  
          # Test for sub-classing of node building.
          global built_it
  
          built_it = None
          assert not built_it
          d1.add_source([SCons.Node.Node()])  # XXX FAKE SUBCLASS ATTRIBUTE
          d1.builder_set(Builder(fs.File))
          d1.reset_executor()
          d1.env_set(Environment())
          d1.build()
          assert built_it
  
          built_it = None
          assert not built_it
          f1.add_source([SCons.Node.Node()])  # XXX FAKE SUBCLASS ATTRIBUTE
          f1.builder_set(Builder(fs.File))
          f1.reset_executor()
          f1.env_set(Environment())
          f1.build()
          assert built_it
  
          def match(path, expect):
              expect = expect.replace('/', os.sep)
              assert path == expect, "path %s != expected %s" % (path, expect)
  
          e1 = fs.Entry("d1")
          assert e1.__class__.__name__ == 'Dir'
          match(e1.get_internal_path(), "d1")
          match(e1.dir.get_internal_path(), ".")
  
          e2 = fs.Entry("d1/f1")
          assert e2.__class__.__name__ == 'File'
          match(e2.get_internal_path(), "d1/f1")
          match(e2.dir.get_internal_path(), "d1")
  
          e3 = fs.Entry("e3")
          assert e3.__class__.__name__ == 'Entry'
          match(e3.get_internal_path(), "e3")
          match(e3.dir.get_internal_path(), ".")
  
          e4 = fs.Entry("d1/e4")
          assert e4.__class__.__name__ == 'Entry'
          match(e4.get_internal_path(), "d1/e4")
          match(e4.dir.get_internal_path(), "d1")
  
          e5 = fs.Entry("e3/e5")
          assert e3.__class__.__name__ == 'Dir'
          match(e3.get_internal_path(), "e3")
          match(e3.dir.get_internal_path(), ".")
          assert e5.__class__.__name__ == 'Entry'
          match(e5.get_internal_path(), "e3/e5")
          match(e5.dir.get_internal_path(), "e3")
  
          e6 = fs.Dir("d1/e4")
          assert e6 is e4
          assert e4.__class__.__name__ == 'Dir'
          match(e4.get_internal_path(), "d1/e4")
          match(e4.dir.get_internal_path(), "d1")
  
          e7 = fs.File("e3/e5")
          assert e7 is e5
          assert e5.__class__.__name__ == 'File'
          match(e5.get_internal_path(), "e3/e5")
          match(e5.dir.get_internal_path(), "e3")
  
          fs.chdir(fs.Dir('subdir'))
          f11 = fs.File("f11")
          match(f11.get_internal_path(), "subdir/f11")
          d12 = fs.Dir("d12")
          e13 = fs.Entry("subdir/e13")
          match(e13.get_internal_path(), "subdir/subdir/e13")
          fs.chdir(fs.Dir('..'))
  
          # Test scanning
          f1.builder_set(Builder(fs.File))
          f1.env_set(Environment())
          xyz = fs.File("xyz")
          f1.builder.target_scanner = Scanner(xyz)
  
          f1.scan()
          assert f1.implicit[0].get_internal_path() == "xyz"
          f1.implicit = []
          f1.scan()
          assert f1.implicit == []
          f1.implicit = None
          f1.scan()
          assert f1.implicit[0].get_internal_path() == "xyz"
  
          # Test underlying scanning functionality in get_found_includes()
          env = Environment()
          f12 = fs.File("f12")
          t1 = fs.File("t1")
  
          deps = f12.get_found_includes(env, None, t1)
          assert deps == [], deps
  
          class MyScanner(Scanner):
              call_count = 0
  
              def __call__(self, node, env, path):
                  self.call_count = self.call_count + 1
                  return Scanner.__call__(self, node, env, path)
  
          s = MyScanner(xyz)
  
          deps = f12.get_found_includes(env, s, t1)
          assert deps == [xyz], deps
          assert s.call_count == 1, s.call_count
  
          f12.built()
  
          deps = f12.get_found_includes(env, s, t1)
          assert deps == [xyz], deps
          assert s.call_count == 2, s.call_count
  
          env2 = Environment()
  
          deps = f12.get_found_includes(env2, s, t1)
          assert deps == [xyz], deps
          assert s.call_count == 3, s.call_count
  
          # Make sure we can scan this file even if the target isn't
          # a file that has a scanner (it might be an Alias, e.g.).
          class DummyNode:
              pass
  
          deps = f12.get_found_includes(env, s, DummyNode())
          assert deps == [xyz], deps
  
          # Test building a file whose directory is not there yet...
          f1 = fs.File(test.workpath("foo/bar/baz/ack"))
          assert not f1.dir.exists()
          f1.prepare()
          f1.build()
          assert f1.dir.exists()
  
          os.chdir('..')
  
          # Test getcwd()
          fs = SCons.Node.FS.FS()
          assert str(fs.getcwd()) == ".", str(fs.getcwd())
          fs.chdir(fs.Dir('subdir'))
          # The cwd's path is always "."
          assert str(fs.getcwd()) == ".", str(fs.getcwd())
          assert fs.getcwd().get_internal_path() == 'subdir', fs.getcwd().get_internal_path()
          fs.chdir(fs.Dir('../..'))
          assert fs.getcwd().get_internal_path() == test.workdir, fs.getcwd().get_internal_path()
  
          f1 = fs.File(test.workpath("do_i_exist"))
          assert not f1.exists()
          test.write("do_i_exist", "\n")
          assert not f1.exists(), "exists() call not cached"
          f1.built()
          assert f1.exists(), "exists() call caching not reset"
          test.unlink("do_i_exist")
          assert f1.exists()
          f1.built()
          assert not f1.exists()
  
          # 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() == bytearray("Foo\x1aBar", 'utf-8'), \
              f1.get_contents()
  
          # This tests to make sure we can decode UTF-8 text files.
          test_string = "Foo\x1aBar"
          test.write("utf8_file", test_string.encode('utf-8'))
          f1 = fs.File(test.workpath("utf8_file"))
          assert f1.get_text_contents() == "Foo\x1aBar", \
              f1.get_text_contents()
  
          # Check for string which doesn't have BOM and isn't valid
          # ASCII
          test_string = b'Gan\xdfauge'
          test.write('latin1_file', test_string)
          f1 = fs.File(test.workpath("latin1_file"))
          assert f1.get_text_contents() == test_string.decode('latin-1'), \
              f1.get_text_contents()
  
          def nonexistent(method, s):
              try:
                  x = method(s, create=0)
              except SCons.Errors.UserError:
                  pass
              else:
                  raise Exception("did not catch expected UserError")
  
          nonexistent(fs.Entry, 'nonexistent')
          nonexistent(fs.Entry, 'nonexistent/foo')
  
          nonexistent(fs.File, 'nonexistent')
          nonexistent(fs.File, 'nonexistent/foo')
  
          nonexistent(fs.Dir, 'nonexistent')
          nonexistent(fs.Dir, 'nonexistent/foo')
  
          test.write("preserve_me", "\n")
          assert os.path.exists(test.workpath("preserve_me"))
          f1 = fs.File(test.workpath("preserve_me"))
          f1.prepare()
          assert os.path.exists(test.workpath("preserve_me"))
  
          test.write("remove_me", "\n")
          assert os.path.exists(test.workpath("remove_me"))
          f1 = fs.File(test.workpath("remove_me"))
          f1.builder = Builder(fs.File)
          f1.env_set(Environment())
          f1.prepare()
          assert not os.path.exists(test.workpath("remove_me"))
  
          e = fs.Entry('e_local')
          assert not hasattr(e, '_local')
          e.set_local()
          assert e._local == 1
          f = fs.File('e_local')
          assert f._local == 1
          f = fs.File('f_local')
          assert f._local == 0
  
          # XXX test_is_up_to_date() for directories
  
          # XXX test_sconsign() for directories
  
          # XXX test_set_signature() for directories
  
          # XXX test_build() for directories
  
          # XXX test_root()
  
          # test Entry.get_contents()
          e = fs.Entry('does_not_exist')
          c = e.get_contents()
          assert c == "", c
          assert e.__class__ == SCons.Node.FS.Entry
  
          test.write("file", "file\n")
          try:
              e = fs.Entry('file')
              c = e.get_contents()
              assert c == bytearray("file\n", 'utf-8'), c
              assert e.__class__ == SCons.Node.FS.File
          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 == "", c')
          except SyntaxError:
              assert c == ""
  
          if sys.platform != 'win32' and 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, e.__class__
              assert c == "", c
              c = e.get_text_contents()
              assert c == "", c
  
          test.write("tstamp", "tstamp\n")
          try:
              # Okay, *this* manipulation accomodates Windows FAT file systems
              # that only have two-second granularity on their timestamps.
              # We round down the current time to the nearest even integer
              # value, subtract two to make sure the timestamp is not "now,"
              # and then convert it back to a float.
              tstamp = float(int(time.time() // 2) * 2) - 2.0
              os.utime(test.workpath("tstamp"), (tstamp - 2.0, tstamp))
              f = fs.File("tstamp")
              t = f.get_timestamp()
              assert t == tstamp, "expected %f, got %f" % (tstamp, t)
          finally:
              test.unlink("tstamp")
  
          test.subdir('tdir1')
          d = fs.Dir('tdir1')
          t = d.get_timestamp()
          assert t == 0, "expected 0, got %s" % str(t)
  
          test.subdir('tdir2')
          f1 = test.workpath('tdir2', 'file1')
          f2 = test.workpath('tdir2', 'file2')
          test.write(f1, 'file1\n')
          test.write(f2, 'file2\n')
          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)
  
          skey = fs.Entry('eee.x').scanner_key()
          assert skey == '.x', skey
          skey = fs.Entry('eee.xyz').scanner_key()
          assert skey == '.xyz', skey
  
          skey = fs.File('fff.x').scanner_key()
          assert skey == '.x', skey
          skey = fs.File('fff.xyz').scanner_key()
          assert skey == '.xyz', skey
  
          skey = fs.Dir('ddd.x').scanner_key()
          assert skey is None, skey
  
          test.write("i_am_not_a_directory", "\n")
          try:
              exc_caught = 0
              try:
                  fs.Dir(test.workpath("i_am_not_a_directory"))
              except TypeError:
                  exc_caught = 1
              assert exc_caught, "Should have caught a TypeError"
          finally:
              test.unlink("i_am_not_a_directory")
  
          exc_caught = 0
          try:
              fs.File(sub_dir)
          except TypeError:
              exc_caught = 1
          assert exc_caught, "Should have caught a TypeError"
  
          # XXX test_is_up_to_date()
  
          d = fs.Dir('dir')
          r = d.remove()
          assert r is None, r
  
          f = fs.File('does_not_exist')
          r = f.remove()
          assert r is None, r
  
          test.write('exists', "exists\n")
          f = fs.File('exists')
          r = f.remove()
          assert r, r
          assert not os.path.exists(test.workpath('exists')), "exists was not removed"
  
          if sys.platform != 'win32' and hasattr(os, 'symlink'):
              symlink = test.workpath('symlink')
              os.symlink(test.workpath('does_not_exist'), symlink)
              assert os.path.islink(symlink)
              f = fs.File('symlink')
              r = f.remove()
              assert r, r
              assert not os.path.islink(symlink), "symlink was not removed"
  
          test.write('can_not_remove', "can_not_remove\n")
          test.writable(test.workpath('.'), 0)
          fp = open(test.workpath('can_not_remove'))
  
          f = fs.File('can_not_remove')
          exc_caught = 0
          try:
              r = f.remove()
          except OSError:
              exc_caught = 1
  
          fp.close()
  
          assert exc_caught, "Should have caught an OSError, r = " + str(r)
  
          f = fs.Entry('foo/bar/baz')
          assert f.for_signature() == 'baz', f.for_signature()
          assert f.get_string(0) == os.path.normpath('foo/bar/baz'), \
              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(dirs, test=test):
              x = test.workpath(*dirs)
              drive, path = os.path.splitdrive(x)
              return 'X:' + path
  
          wp = drive_workpath([''])
  
          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(['foo'])
          foo_bar = drive_workpath(['foo', 'bar'])
          sub = drive_workpath(['sub', ''])
          sub_dir = drive_workpath(['sub', 'dir', ''])
          sub_dir_foo = drive_workpath(['sub', 'dir', 'foo', ''])
          sub_dir_foo_bar = drive_workpath(['sub', 'dir', 'foo', 'bar', ''])
          sub_foo = drive_workpath(['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.get_internal_path() == path, \
                  "dir.path %s != expected path %s" % \
                  (dir.get_internal_path(), path)
              assert str(dir) == path, \
                  "str(dir) %s != expected path %s" % \
                  (str(dir), path)
              assert dir.up().get_internal_path() == up_path, \
                  "dir.up().path %s != expected parent path %s" % \
                  (dir.up().get_internal_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()
  
              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()
  
      def test_unc_path(self):
          """Test UNC path look-ups"""
  
          test = self.test
  
          test.subdir('sub', ['sub', 'dir'])
  
          def strip_slash(p):
              if p[-1] == os.sep and len(p) > 3:
                  p = p[:-1]
              return p
  
          def unc_workpath(dirs, test=test):
              import ntpath
              x = test.workpath(*dirs)
              drive, path = ntpath.splitdrive(x)
              try:
                  unc, path = ntpath.splitunc(path)
              except AttributeError:
                  # could be python 3.7 or newer, make sure splitdrive can do UNC
                  assert ntpath.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive'
              path = strip_slash(path)
              return '//' + path[1:]
  
          wp = unc_workpath([''])
  
          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]
  
          tmp_foo = os.path.join(tmp, 'foo')
  
          foo = unc_workpath(['foo'])
          foo_bar = unc_workpath(['foo', 'bar'])
          sub = unc_workpath(['sub', ''])
          sub_dir = unc_workpath(['sub', 'dir', ''])
          sub_dir_foo = unc_workpath(['sub', 'dir', 'foo', ''])
          sub_dir_foo_bar = unc_workpath(['sub', 'dir', 'foo', 'bar', ''])
          sub_foo = unc_workpath(['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)
  
              if path == os.sep + os.sep:
                  name = os.sep + os.sep
              else:
                  name = path.split(os.sep)[-1]
  
              if dir.up() is None:
                  dir_up_path = dir.get_internal_path()
              else:
                  dir_up_path = dir.up().get_internal_path()
  
              assert dir.name == name, \
                  "dir.name %s != expected name %s" % \
                  (dir.name, name)
              assert dir.get_internal_path() == path, \
                  "dir.path %s != expected path %s" % \
                  (dir.get_internal_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().get_internal_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()
  
              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('//foo', '//foo', '//')
                  Dir_test('//foo/bar', '//foo/bar', '//foo')
                  Dir_test('//', '//', '//')
                  Dir_test('//..', '//', '//')
                  Dir_test('//foo/..', '//', '//')
                  Dir_test('//../foo', '//foo', '//')
                  Dir_test('//.', '//', '//')
                  Dir_test('//./.', '//', '//')
                  Dir_test('//foo/./bar', '//foo/bar', '//foo')
                  Dir_test('//foo/../bar', '//bar', '//')
                  Dir_test('//foo/../../bar', '//bar', '//')
                  Dir_test('//foo/bar/../..', '//', '//')
                  Dir_test('#//', wp, tmp)
                  Dir_test('#//../foo', tmp_foo, tmp)
                  Dir_test('#//../foo', tmp_foo, tmp)
                  Dir_test('#//foo/bar', foo_bar, foo)
                  Dir_test('#//foo/bar', foo_bar, foo)
                  Dir_test('#//', wp, tmp)
          finally:
              os.path = save_os_path
              os.sep = save_os_sep
              SCons.Node.FS.initialize_do_splitdrive()
  
      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 a Dir lookup"""
          test = self.test
          fs = self.fs
  
          test.subdir('subdir')
          test.write(['subdir', 'build'], "subdir/build\n")
  
          subdir = fs.Dir('subdir')
          fs.chdir(subdir, change_os_dir=1)
          self.fs._lookup('#build/file', subdir, SCons.Node.FS.File)
  
      def test_above_root(self):
          """Testing looking up a path above the root directory"""
          test = self.test
          fs = self.fs
  
          d1 = fs.Dir('d1')
          d2 = d1.Dir('d2')
          dirs = os.path.normpath(d2.get_abspath()).split(os.sep)
          above_path = os.path.join(*['..'] * len(dirs) + ['above'])
          above = d2.Dir(above_path)
  
      def test_lookup_abs(self):
          """Exercise the _lookup_abs function"""
          test = self.test
          fs = self.fs
  
          root = fs.Dir('/')
          d = root._lookup_abs('/tmp/foo-nonexistent/nonexistent-dir', SCons.Node.FS.Dir)
          assert d.__class__ == SCons.Node.FS.Dir, str(d.__class__)
  
      @unittest.skipUnless(sys.platform == "win32", "requires Windows")
      def test_lookup_uncpath(self):
          """Testing looking up a UNC path on Windows"""
          test = self.test
          fs = self.fs
          path = '//servername/C$/foo'
          f = self.fs._lookup('//servername/C$/foo', fs.Dir('#'), SCons.Node.FS.File)
          # before the fix in this commit, this returned 'C:\servername\C$\foo'
          # Should be a normalized Windows UNC path as below.
          assert str(f) == r'\\servername\C$\foo', \
              'UNC path %s got looked up as %s' % (path, f)
  
      @unittest.skipUnless(sys.platform.startswith == "win32", "requires Windows")
      def test_unc_drive_letter(self):
          """Test drive-letter lookup for windows UNC-style directories"""
          share = self.fs.Dir(r'\\SERVER\SHARE\Directory')
          assert str(share) == r'\\SERVER\SHARE\Directory', str(share)
  
      @unittest.skipUnless(sys.platform == "win32", "requires Windows")
      def test_UNC_dirs_2689(self):
          """Test some UNC dirs that printed incorrectly and/or caused
          infinite recursion errors prior to r5180 (SCons 2.1)."""
          fs = self.fs
          p = fs.Dir(r"\\computername\sharename").get_abspath()
          assert p == r"\\computername\sharename", p
          p = fs.Dir(r"\\\computername\sharename").get_abspath()
          assert p == r"\\computername\sharename", p
  
      def test_rel_path(self):
          """Test the rel_path() method"""
          test = self.test
          fs = self.fs
  
          d1 = fs.Dir('d1')
          d1_f = d1.File('f')
          d1_d2 = d1.Dir('d2')
          d1_d2_f = d1_d2.File('f')
  
          d3 = fs.Dir('d3')
          d3_f = d3.File('f')
          d3_d4 = d3.Dir('d4')
          d3_d4_f = d3_d4.File('f')
  
          cases = [
              d1, d1, '.',
              d1, d1_f, 'f',
              d1, d1_d2, 'd2',
              d1, d1_d2_f, 'd2/f',
              d1, d3, '../d3',
              d1, d3_f, '../d3/f',
              d1, d3_d4, '../d3/d4',
              d1, d3_d4_f, '../d3/d4/f',
  
              d1_f, d1, '.',
              d1_f, d1_f, 'f',
              d1_f, d1_d2, 'd2',
              d1_f, d1_d2_f, 'd2/f',
              d1_f, d3, '../d3',
              d1_f, d3_f, '../d3/f',
              d1_f, d3_d4, '../d3/d4',
              d1_f, d3_d4_f, '../d3/d4/f',
  
              d1_d2, d1, '..',
              d1_d2, d1_f, '../f',
              d1_d2, d1_d2, '.',
              d1_d2, d1_d2_f, 'f',
              d1_d2, d3, '../../d3',
              d1_d2, d3_f, '../../d3/f',
              d1_d2, d3_d4, '../../d3/d4',
              d1_d2, d3_d4_f, '../../d3/d4/f',
  
              d1_d2_f, d1, '..',
              d1_d2_f, d1_f, '../f',
              d1_d2_f, d1_d2, '.',
              d1_d2_f, d1_d2_f, 'f',
              d1_d2_f, d3, '../../d3',
              d1_d2_f, d3_f, '../../d3/f',
              d1_d2_f, d3_d4, '../../d3/d4',
              d1_d2_f, d3_d4_f, '../../d3/d4/f',
          ]
  
          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:
              dir, other, expect = cases[:3]
              expect = os.path.normpath(expect)
              del cases[:3]
              result = dir.rel_path(other)
              if result != expect:
                  if failed == 0: print()
                  fmt = "    dir_path(%(dir)s, %(other)s) => '%(result)s' did not match '%(expect)s'"
                  print(fmt % locals())
                  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 MyProxy(SCons.Util.Proxy):
              __str__ = SCons.Util.Delegate('__str__')
  
          p = MyProxy(f1)
          f2 = self.fs.Entry(p)
          assert f1 is f2, (f1, str(f1), f2, str(f2))
  
  
  class DirTestCase(_tempdirTestCase):
  
      def test__morph(self):
          """Test handling of actions when morphing an Entry into a Dir"""
          test = self.test
          e = self.fs.Entry('eee')
          x = e.get_executor()
          x.add_pre_action('pre')
          x.add_post_action('post')
          e.must_be_same(SCons.Node.FS.Dir)
          a = x.get_action_list()
          assert 'pre' in a, a
          assert 'post' in a, 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.get_internal_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'))
  
          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_hash_chunksize(self):
          """
          Test verifying that File.get_csig() correctly uses hash_chunksize. This
          variable is documented as the hash chunksize in kilobytes. This test
          verifies that if the file size is less than the hash chunksize,
          get_contents() is called; otherwise, it verifies that get_contents()
          is not called.
          """
          chunksize_bytes = SCons.Node.FS.File.hash_chunksize
          test = self.test
  
          test.subdir('chunksize_dir')
          test.write(['chunksize_dir', 'f1'], 'a' * (chunksize_bytes // 1024 - 1))
          test.write(['chunksize_dir', 'f2'], 'a' * (chunksize_bytes // 1024))
          test.write(['chunksize_dir', 'f3'], 'a' * (chunksize_bytes + 1))
  
          dir = self.fs.Dir('chunksize_dir')
          f1 = dir.File('f1')
          f2 = dir.File('f2')
          f3 = dir.File('f3')
  
          # Expect f1 and f2 to call get_contents(), while f3 will not because it
          # should do reads of chunksize kilobytes at a time.
          expected_get_contents_calls = {f1, f2}
          self.actual_get_contents_calls = 0
  
          def get_contents_override(file_object):
              self.actual_get_contents_calls += 1
              if file_object in expected_get_contents_calls:
                  return file_object._old_get_contents()
              else:
                  raise Exception('get_contents was unexpectedly called on node '
                                  '%s' % file_object)
  
          SCons.Node.FS.File._old_get_contents = SCons.Node.FS.File.get_contents
          SCons.Node.FS.File.get_contents = get_contents_override.__get__(
              None, SCons.Node.FS)
  
          # Call get_csig() to test get_contents() usage. The actual results of
          # the calls to get_csig() are not relevant for this test. If an
          # exception is raised, we must first reset the get_contents function
          # before reraising it or other tests will fail too.
          exception = None
          try:
              f1.get_csig()
              f2.get_csig()
              f3.get_csig()
          except Exception as e:
              exception = e
  
          SCons.Node.FS.File.get_contents = SCons.Node.FS.File._old_get_contents
          delattr(SCons.Node.FS.File, '_old_get_contents')
  
          if exception:
              raise exception
  
          assert self.actual_get_contents_calls == len(expected_get_contents_calls), \
              self.actual_get_contents_calls
  
      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.get_internal_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.get_internal_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
          """
          test = self.test
  
          does_not_exist = self.fs.Dir('does_not_exist')
          assert not does_not_exist.entry_exists_on_disk('foo')
  
          test.subdir('d')
          test.write(['d', 'exists'], "d/exists\n")
          test.write(['d', 'Case-Insensitive'], "d/Case-Insensitive\n")
  
          d = self.fs.Dir('d')
          assert d.entry_exists_on_disk('exists')
          assert not d.entry_exists_on_disk('does_not_exist')
  
          if os.path.normcase("TeSt") != os.path.normpath("TeSt") or sys.platform == "cygwin":
              assert d.entry_exists_on_disk('case-insensitive')
  
      def test_rentry_exists_on_disk(self):
          """Test the Dir.rentry_exists_on_disk() method
          """
          test = self.test
  
          does_not_exist = self.fs.Dir('does_not_exist')
          assert not does_not_exist.rentry_exists_on_disk('foo')
  
          test.subdir('d')
          test.write(['d', 'exists'], "d/exists\n")
          test.write(['d', 'Case-Insensitive'], "d/Case-Insensitive\n")
  
          test.subdir('r')
          test.write(['r', 'rexists'], "r/rexists\n")
  
          d = self.fs.Dir('d')
          r = self.fs.Dir('r')
          d.addRepository(r)
  
          assert d.rentry_exists_on_disk('exists')
          assert d.rentry_exists_on_disk('rexists')
          assert not d.rentry_exists_on_disk('does_not_exist')
  
          if os.path.normcase("TeSt") != os.path.normpath("TeSt") or sys.platform == "cygwin":
              assert d.rentry_exists_on_disk('case-insensitive')
  
      def test_srcdir_list(self):
          """Test the Dir.srcdir_list() method
          """
          src = self.fs.Dir('src')
          bld = self.fs.Dir('bld')
          sub1 = bld.Dir('sub')
          sub2 = sub1.Dir('sub')
          sub3 = sub2.Dir('sub')
          self.fs.VariantDir(bld, src, duplicate=0)
          self.fs.VariantDir(sub2, src, duplicate=0)
  
          def check(result, expect):
              result = list(map(str, result))
              expect = list(map(os.path.normpath, expect))
              assert result == expect, result
  
          s = src.srcdir_list()
          check(s, [])
  
          s = bld.srcdir_list()
          check(s, ['src'])
  
          s = sub1.srcdir_list()
          check(s, ['src/sub'])
  
          s = sub2.srcdir_list()
          check(s, ['src', 'src/sub/sub'])
  
          s = sub3.srcdir_list()
          check(s, ['src/sub', 'src/sub/sub/sub'])
  
          self.fs.VariantDir('src/b1/b2', 'src')
          b1 = src.Dir('b1')
          b1_b2 = b1.Dir('b2')
          b1_b2_b1 = b1_b2.Dir('b1')
          b1_b2_b1_b2 = b1_b2_b1.Dir('b2')
          b1_b2_b1_b2_sub = b1_b2_b1_b2.Dir('sub')
  
          s = b1.srcdir_list()
          check(s, [])
  
          s = b1_b2.srcdir_list()
          check(s, ['src'])
  
          s = b1_b2_b1.srcdir_list()
          check(s, ['src/b1'])
  
          s = b1_b2_b1_b2.srcdir_list()
          check(s, ['src/b1/b2'])
  
          s = b1_b2_b1_b2_sub.srcdir_list()
          check(s, ['src/b1/b2/sub'])
  
      def test_srcdir_duplicate(self):
          """Test the Dir.srcdir_duplicate() method
          """
          test = self.test
  
          test.subdir('src0')
          test.write(['src0', 'exists'], "src0/exists\n")
  
          bld0 = self.fs.Dir('bld0')
          src0 = self.fs.Dir('src0')
          self.fs.VariantDir(bld0, src0, duplicate=0)
  
          n = bld0.srcdir_duplicate('does_not_exist')
          assert n is None, n
          assert not os.path.exists(test.workpath('bld0', 'does_not_exist'))
  
          n = bld0.srcdir_duplicate('exists')
          assert str(n) == os.path.normpath('src0/exists'), str(n)
          assert not os.path.exists(test.workpath('bld0', 'exists'))
  
          test.subdir('src1')
          test.write(['src1', 'exists'], "src0/exists\n")
  
          bld1 = self.fs.Dir('bld1')
          src1 = self.fs.Dir('src1')
          self.fs.VariantDir(bld1, src1, duplicate=1)
  
          n = bld1.srcdir_duplicate('does_not_exist')
          assert n is None, n
          assert not os.path.exists(test.workpath('bld1', 'does_not_exist'))
  
          n = bld1.srcdir_duplicate('exists')
          assert str(n) == os.path.normpath('bld1/exists'), str(n)
          assert os.path.exists(test.workpath('bld1', 'exists'))
  
      def test_srcdir_find_file(self):
          """Test the Dir.srcdir_find_file() method
          """
          test = self.test
  
          def return_true(node):
              return 1
  
          SCons.Node._is_derived_map[2] = return_true
          SCons.Node._exists_map[5] = return_true
  
          test.subdir('src0')
          test.write(['src0', 'on-disk-f1'], "src0/on-disk-f1\n")
          test.write(['src0', 'on-disk-f2'], "src0/on-disk-f2\n")
          test.write(['src0', 'on-disk-e1'], "src0/on-disk-e1\n")
          test.write(['src0', 'on-disk-e2'], "src0/on-disk-e2\n")
  
          bld0 = self.fs.Dir('bld0')
          src0 = self.fs.Dir('src0')
          self.fs.VariantDir(bld0, src0, duplicate=0)
  
          derived_f = src0.File('derived-f')
          derived_f._func_is_derived = 2
          exists_f = src0.File('exists-f')
          exists_f._func_exists = 5
  
          derived_e = src0.Entry('derived-e')
          derived_e._func_is_derived = 2
          exists_e = src0.Entry('exists-e')
          exists_e._func_exists = 5
  
          def check(result, expect):
              result = list(map(str, result))
              expect = list(map(os.path.normpath, expect))
              assert result == expect, result
  
          # First check from the source directory.
          n = src0.srcdir_find_file('does_not_exist')
          assert n == (None, None), n
  
          n = src0.srcdir_find_file('derived-f')
          check(n, ['src0/derived-f', 'src0'])
          n = src0.srcdir_find_file('exists-f')
          check(n, ['src0/exists-f', 'src0'])
          n = src0.srcdir_find_file('on-disk-f1')
          check(n, ['src0/on-disk-f1', 'src0'])
  
          n = src0.srcdir_find_file('derived-e')
          check(n, ['src0/derived-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 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('exists-f')
          check(n, ['src0/exists-f', 'bld0'])
          n = bld0.srcdir_find_file('on-disk-f2')
          check(n, ['src0/on-disk-f2', 'bld0'])
  
          n = bld0.srcdir_find_file('derived-e')
          check(n, ['src0/derived-e', 'bld0'])
          n = bld0.srcdir_find_file('exists-e')
          check(n, ['src0/exists-e', 'bld0'])
          n = bld0.srcdir_find_file('on-disk-e2')
          check(n, ['src0/on-disk-e2', 'bld0'])
  
          test.subdir('src1')
          test.write(['src1', 'on-disk-f1'], "src1/on-disk-f1\n")
          test.write(['src1', 'on-disk-f2'], "src1/on-disk-f2\n")
          test.write(['src1', 'on-disk-e1'], "src1/on-disk-e1\n")
          test.write(['src1', 'on-disk-e2'], "src1/on-disk-e2\n")
  
          bld1 = self.fs.Dir('bld1')
          src1 = self.fs.Dir('src1')
          self.fs.VariantDir(bld1, src1, duplicate=1)
  
          derived_f = src1.File('derived-f')
          derived_f._func_is_derived = 2
          exists_f = src1.File('exists-f')
          exists_f._func_exists = 5
  
          derived_e = src1.Entry('derived-e')
          derived_e._func_is_derived = 2
          exists_e = src1.Entry('exists-e')
          exists_e._func_exists = 5
  
          # First check from the source directory.
          n = src1.srcdir_find_file('does_not_exist')
          assert n == (None, None), n
  
          n = src1.srcdir_find_file('derived-f')
          check(n, ['src1/derived-f', 'src1'])
          n = src1.srcdir_find_file('exists-f')
          check(n, ['src1/exists-f', 'src1'])
          n = src1.srcdir_find_file('on-disk-f1')
          check(n, ['src1/on-disk-f1', 'src1'])
  
          n = src1.srcdir_find_file('derived-e')
          check(n, ['src1/derived-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 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('exists-f')
          check(n, ['bld1/exists-f', 'src1'])
          n = bld1.srcdir_find_file('on-disk-f2')
          check(n, ['bld1/on-disk-f2', 'bld1'])
  
          n = bld1.srcdir_find_file('derived-e')
          check(n, ['bld1/derived-e', 'src1'])
          n = bld1.srcdir_find_file('exists-e')
          check(n, ['bld1/exists-e', 'src1'])
          n = bld1.srcdir_find_file('on-disk-e2')
          check(n, ['bld1/on-disk-e2', 'bld1'])
  
      def test_dir_on_disk(self):
          """Test the Dir.dir_on_disk() method"""
          self.test.subdir('sub', ['sub', 'exists'])
          self.test.write(['sub', 'file'], "self/file\n")
          sub = self.fs.Dir('sub')
  
          r = sub.dir_on_disk('does_not_exist')
          assert not r, r
  
          r = sub.dir_on_disk('exists')
          assert r, r
  
          r = sub.dir_on_disk('file')
          assert not r, r
  
      def test_file_on_disk(self):
          """Test the Dir.file_on_disk() method"""
          self.test.subdir('sub', ['sub', 'dir'])
          self.test.write(['sub', 'exists'], "self/exists\n")
          sub = self.fs.Dir('sub')
  
          r = sub.file_on_disk('does_not_exist')
          assert not r, r
  
          r = sub.file_on_disk('exists')
          assert r, r
  
          r = sub.file_on_disk('dir')
          assert not r, r
  
  
  class EntryTestCase(_tempdirTestCase):
      def test_runTest(self):
          """Test methods specific to the Entry sub-class.
          """
          test = TestCmd(workdir='')
          # FS doesn't like the cwd to be something other than its root.
          os.chdir(test.workpath(""))
  
          fs = SCons.Node.FS.FS()
  
          e1 = fs.Entry('e1')
          e1.rfile()
          assert e1.__class__ is SCons.Node.FS.File, e1.__class__
  
          test.subdir('e3d')
          test.write('e3f', "e3f\n")
  
          e3d = fs.Entry('e3d')
          e3d.get_contents()
          assert e3d.__class__ is SCons.Node.FS.Dir, e3d.__class__
  
          e3f = fs.Entry('e3f')
          e3f.get_contents()
          assert e3f.__class__ is SCons.Node.FS.File, e3f.__class__
  
          e3n = fs.Entry('e3n')
          e3n.get_contents()
          assert e3n.__class__ is SCons.Node.FS.Entry, e3n.__class__
  
          test.subdir('e4d')
          test.write('e4f', "e4f\n")
  
          e4d = fs.Entry('e4d')
          exists = e4d.exists()
          assert e4d.__class__ is SCons.Node.FS.Dir, e4d.__class__
          assert exists, "e4d does not exist?"
  
          e4f = fs.Entry('e4f')
          exists = e4f.exists()
          assert e4f.__class__ is SCons.Node.FS.File, e4f.__class__
          assert exists, "e4f does not exist?"
  
          e4n = fs.Entry('e4n')
          exists = e4n.exists()
          assert e4n.__class__ is SCons.Node.FS.File, e4n.__class__
          assert not exists, "e4n exists?"
  
          class MyCalc:
              def __init__(self, val):
                  self.max_drift = 0
  
                  class M:
                      def __init__(self, val):
                          self.val = val
  
                      def collect(self, args):
                          result = 0
                          for a in args:
                              result += a
                          return result
  
                      def signature(self, executor):
                          return self.val + 222
  
                  self.module = M(val)
  
          test.subdir('e5d')
          test.write('e5f', "e5f\n")
  
      def test_Entry_Entry_lookup(self):
          """Test looking up an Entry within another Entry"""
          self.fs.Entry('#topdir')
          self.fs.Entry('#topdir/a/b/c')
  
  
  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')
          # This simulates that the SConscript file that defined
          # fff is in subdir/.
          fff.cwd = self.fs.Dir('subdir')
          d1 = self.fs.Dir('subdir/d1')
          d2 = self.fs.Dir('subdir/d2')
          dirs = fff.Dirs(['d1', 'd2'])
          assert dirs == [d1, d2], list(map(str, dirs))
  
      def test_exists(self):
          """Test the File.exists() method"""
          fs = self.fs
          test = self.test
  
          src_f1 = fs.File('src/f1')
          assert not src_f1.exists(), "%s apparently exists?" % src_f1
  
          test.subdir('src')
          test.write(['src', 'f1'], "src/f1\n")
  
          assert not src_f1.exists(), "%s did not cache previous exists() value" % src_f1
          src_f1.clear()
          assert src_f1.exists(), "%s apparently does not exist?" % src_f1
  
          test.subdir('build')
          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)
          assert os.path.exists(build_f1.get_abspath()), "%s did not get duplicated on disk" % build_f1.get_abspath()
  
          test.unlink(['src', 'f1'])
          src_f1.clear()  # so the next exists() call will look on disk again
  
          assert build_f1.exists(), "%s did not cache previous exists() value" % build_f1
          build_f1.clear()
          build_f1.linked = None
          assert not build_f1.exists(), "%s did not realize that %s disappeared" % (build_f1, src_f1)
          assert not os.path.exists(build_f1.get_abspath()), "%s did not get removed after %s was removed" % (
          build_f1, src_f1)
  
      def test_changed(self):
          """
          Verify that changes between BuildInfo's list of souces, depends, and implicit
          dependencies do not corrupt content signature values written to .SConsign
          when using CacheDir and Timestamp-MD5 decider.
          This is for issue #2980
          """
  
          # node should have
          # 1 source (for example main.cpp)
          # 0 depends
          # N implicits (for example ['alpha.h', 'beta.h', 'gamma.h', '/usr/bin/g++'])
  
          class ChangedNode(SCons.Node.FS.File):
              def __init__(self, name, directory=None, fs=None):
                  SCons.Node.FS.File.__init__(self, name, directory, fs)
                  self.name = name
                  self.Tag('found_includes', [])
                  self.stored_info = None
                  self.build_env = None
                  self.changed_since_last_build = 4
                  self.timestamp = 1
  
              def get_stored_info(self):
                  return self.stored_info
  
              def get_build_env(self):
                  return self.build_env
  
              def get_timestamp(self):
                  """ Fake timestamp so they always match"""
                  return self.timestamp
  
              def get_contents(self):
                  return self.name
  
              def get_ninfo(self):
                  """ mocked to ensure csig will equal the filename"""
                  if self.ninfo is not None:
                      return self.ninfo
                  self.ninfo = FakeNodeInfo(self.name, self.timestamp)
                  return self.ninfo
  
              def get_csig(self):
                  ninfo = self.get_ninfo()
                  try:
                      return ninfo.csig
                  except AttributeError:
                      pass
  
                  return "Should Never Happen"
  
          class ChangedEnvironment(SCons.Environment.Base):
  
              def __init__(self):
                  SCons.Environment.Base.__init__(self)
                  self.decide_source = self._changed_timestamp_then_content
  
          class FakeNodeInfo:
              def __init__(self, csig, timestamp):
                  self.csig = csig
                  self.timestamp = timestamp
  
          # Create nodes
          fs = SCons.Node.FS.FS()
          d = self.fs.Dir('.')
  
          node = ChangedNode('main.o', d, fs)  # main.o
          s1 = ChangedNode('main.cpp', d, fs)  # main.cpp
          s1.timestamp = 2  # this changed
          i1 = ChangedNode('alpha.h', d, fs)  # alpha.h - The bug is caused because the second build adds this file
          i1.timestamp = 2  # This is the new file.
          i2 = ChangedNode('beta.h', d, fs)  # beta.h
          i3 = ChangedNode('gamma.h', d, fs)  # gamma.h - In the bug beta.h's csig from binfo overwrites this ones
          i4 = ChangedNode('/usr/bin/g++', d, fs)  # /usr/bin/g++
  
          node.add_source([s1])
          node.add_dependency([])
          node.implicit = [i1, i2, i3, i4]
          node.implicit_set = set()
          # node._add_child(node.implicit, node.implicit_set, [n7, n8, n9])
          # node._add_child(node.implicit, node.implicit_set, [n10, n11, n12])
  
          # Mock out node's scan method
          # node.scan = lambda *args: None
  
          # Mock out nodes' children() ?
          # Should return Node's.
          # All those nodes should have changed_since_last_build set to match Timestamp-MD5's
          # decider method...
  
          # Generate sconsign entry needed
          sconsign_entry = SCons.SConsign.SConsignEntry()
          sconsign_entry.binfo = node.new_binfo()
          sconsign_entry.ninfo = node.new_ninfo()
  
          # mock out loading info from sconsign
          # This will cause node.get_stored_info() to return our freshly created sconsign_entry
          node.stored_info = sconsign_entry
  
          # binfo = information from previous build (from sconsign)
          # We'll set the following attributes (which are lists): "bsources", "bsourcesigs",
          # "bdepends","bdependsigs", "bimplicit", "bimplicitsigs"
          bi = sconsign_entry.binfo
          bi.bsources = ['main.cpp']
          bi.bsourcesigs = [FakeNodeInfo('main.cpp', 1), ]
  
          bi.bdepends = []
          bi.bdependsigs = []
  
          bi.bimplicit = ['beta.h', 'gamma.h']
          bi.bimplicitsigs = [FakeNodeInfo('beta.h', 1), FakeNodeInfo('gamma.h', 1)]
  
          ni = sconsign_entry.ninfo
          # We'll set the following attributes (which are lists): sources, depends, implicit lists
  
          # Set timestamp-md5
          # Call changed
          # Check results
          node.build_env = ChangedEnvironment()
  
          changed = node.changed()
  
          # change to true to debug
          if False:
              print("Changed:%s" % changed)
              print("%15s -> csig:%s" % (s1.name, s1.ninfo.csig))
              print("%15s -> csig:%s" % (i1.name, i1.ninfo.csig))
              print("%15s -> csig:%s" % (i2.name, i2.ninfo.csig))
              print("%15s -> csig:%s" % (i3.name, i3.ninfo.csig))
              print("%15s -> csig:%s" % (i4.name, i4.ninfo.csig))
  
          self.assertEqual(
              i2.name,
              i2.ninfo.csig,
              "gamma.h's fake csig should equal gamma.h but equals:%s" % i2.ninfo.csig,
          )
  
  
  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 = sorted(r, key=lambda a: a.get_internal_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):
          _tempdirTestCase.setUp(self)
  
          self.test.subdir('rep1', 'rep2', 'rep3', 'work')
  
          self.rep1 = self.test.workpath('rep1')
          self.rep2 = self.test.workpath('rep2')
          self.rep3 = self.test.workpath('rep3')
  
          os.chdir(self.test.workpath('work'))
  
          self.fs = SCons.Node.FS.FS()
          self.fs.Repository(self.rep1, self.rep2, self.rep3)
  
      def test_getRepositories(self):
          """Test the Dir.getRepositories() method"""
          self.fs.Repository('foo')
          self.fs.Repository(os.path.join('foo', 'bar'))
          self.fs.Repository('bar/foo')
          self.fs.Repository('bar')
  
          expect = [
              self.rep1,
              self.rep2,
              self.rep3,
              'foo',
              os.path.join('foo', 'bar'),
              os.path.join('bar', 'foo'),
              'bar'
          ]
  
          rep = self.fs.Dir('#').getRepositories()
          r = [os.path.normpath(str(x)) for x in rep]
          assert r == expect, r
  
      def test_get_all_rdirs(self):
          """Test the Dir.get_all_rdirs() method"""
          self.fs.Repository('foo')
          self.fs.Repository(os.path.join('foo', 'bar'))
          self.fs.Repository('bar/foo')
          self.fs.Repository('bar')
  
          expect = [
              '.',
              self.rep1,
              self.rep2,
              self.rep3,
              'foo',
              os.path.join('foo', 'bar'),
              os.path.join('bar', 'foo'),
              'bar'
          ]
  
          rep = self.fs.Dir('#').get_all_rdirs()
          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 r is not d2, r
          r = str(r)
          assert r == os.path.join(self.rep1, 'd2'), r
  
          r = d3.rentry()
          assert r is not 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 r is not e2, r
          r = str(r)
          assert r == os.path.join(self.rep1, 'e2'), r
  
          r = e3.rentry()
          assert r is not 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 r is not f2, r
          r = str(r)
          assert r == os.path.join(self.rep1, 'f2'), r
  
          r = f3.rentry()
          assert r is not f3, r
          r = str(r)
          assert r == os.path.join(self.rep2, 'f3'), r
  
      def test_rdir(self):
          """Test the Dir.rdir() method"""
  
          def return_true(obj):
              return 1
  
          def return_false(obj):
              return 0
  
          SCons.Node._exists_map[5] = return_true
          SCons.Node._exists_map[6] = return_false
          SCons.Node._is_derived_map[2] = return_true
          SCons.Node._is_derived_map[3] = return_false
  
          d1 = self.fs.Dir('d1')
          d2 = self.fs.Dir('d2')
          d3 = self.fs.Dir('d3')
  
          self.test.subdir([self.rep1, 'd2'])
          self.test.write([self.rep2, 'd3'], "")
          self.test.subdir([self.rep3, 'd3'])
  
          r = d1.rdir()
          assert r is d1, r
  
          r = d2.rdir()
          assert r is not d2, r
          r = str(r)
          assert r == os.path.join(self.rep1, 'd2'), r
  
          r = d3.rdir()
          assert r is not d3, r
          r = str(r)
          assert r == os.path.join(self.rep3, 'd3'), r
  
          e1 = self.fs.Dir('e1')
          e1._func_exists = 6
          e2 = self.fs.Dir('e2')
          e2._func_exists = 6
  
          # Make sure we match entries in repositories,
          # regardless of whether they're derived or not.
  
          re1 = self.fs.Entry(os.path.join(self.rep1, 'e1'))
          re1._func_exists = 5
          re1._func_is_derived = 2
          re2 = self.fs.Entry(os.path.join(self.rep2, 'e2'))
          re2._func_exists = 5
          re2._func_is_derived = 3
  
          r = e1.rdir()
          assert r is re1, r
  
          r = e2.rdir()
          assert r is re2, r
  
      def test_rfile(self):
          """Test the File.rfile() method"""
  
          def return_true(obj):
              return 1
  
          def return_false(obj):
              return 0
  
          SCons.Node._exists_map[5] = return_true
          SCons.Node._exists_map[6] = return_false
          SCons.Node._is_derived_map[2] = return_true
          SCons.Node._is_derived_map[3] = return_false
  
          f1 = self.fs.File('f1')
          f2 = self.fs.File('f2')
          f3 = self.fs.File('f3')
  
          self.test.write([self.rep1, 'f2'], "")
          self.test.subdir([self.rep2, 'f3'])
          self.test.write([self.rep3, 'f3'], "")
  
          r = f1.rfile()
          assert r is f1, r
  
          r = f2.rfile()
          assert r is not f2, r
          r = str(r)
          assert r == os.path.join(self.rep1, 'f2'), r
  
          r = f3.rfile()
          assert r is not f3, r
          r = f3.rstr()
          assert r == os.path.join(self.rep3, 'f3'), r
  
          e1 = self.fs.File('e1')
          e1._func_exists = 6
          e2 = self.fs.File('e2')
          e2._func_exists = 6
  
          # Make sure we match entries in repositories,
          # regardless of whether they're derived or not.
  
          re1 = self.fs.Entry(os.path.join(self.rep1, 'e1'))
          re1._func_exists = 5
          re1._func_is_derived = 2
          re2 = self.fs.Entry(os.path.join(self.rep2, 'e2'))
          re2._func_exists = 5
          re2._func_is_derived = 3
  
          r = e1.rfile()
          assert r is re1, r
  
          r = e2.rfile()
          assert r is re2, r
  
      def test_Rfindalldirs(self):
          """Test the Rfindalldirs() methods"""
          fs = self.fs
          test = self.test
  
          d1 = fs.Dir('d1')
          d2 = fs.Dir('d2')
          rep1_d1 = fs.Dir(test.workpath('rep1', 'd1'))
          rep2_d1 = fs.Dir(test.workpath('rep2', 'd1'))
          rep3_d1 = fs.Dir(test.workpath('rep3', 'd1'))
          sub = fs.Dir('sub')
          sub_d1 = sub.Dir('d1')
          rep1_sub_d1 = fs.Dir(test.workpath('rep1', 'sub', 'd1'))
          rep2_sub_d1 = fs.Dir(test.workpath('rep2', 'sub', 'd1'))
          rep3_sub_d1 = fs.Dir(test.workpath('rep3', 'sub', 'd1'))
  
          r = fs.Top.Rfindalldirs((d1,))
          assert r == [d1], list(map(str, r))
  
          r = fs.Top.Rfindalldirs((d1, d2))
          assert r == [d1, d2], list(map(str, r))
  
          r = fs.Top.Rfindalldirs(('d1',))
          assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(map(str, r))
  
          r = fs.Top.Rfindalldirs(('#d1',))
          assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(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 = sub.Rfindalldirs(('#d1',))
          assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(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"""
          fs = self.fs
          test = self.test
  
          test.write([self.rep1, 'f2'], "")
          test.write([self.rep2, "i_exist"], "\n")
          test.write(["work", "i_exist_too"], "\n")
  
          fs.VariantDir('build', '.')
  
          f = fs.File(test.workpath("work", "i_do_not_exist"))
          assert not f.rexists()
  
          f = fs.File(test.workpath("work", "i_exist"))
          assert f.rexists()
  
          f = fs.File(test.workpath("work", "i_exist_too"))
          assert f.rexists()
  
          f1 = fs.File(os.path.join('build', 'f1'))
          assert not f1.rexists()
  
          f2 = fs.File(os.path.join('build', 'f2'))
          assert f2.rexists()
  
      def test_FAT_timestamps(self):
          """Test repository timestamps on FAT file systems"""
          fs = self.fs
          test = self.test
  
          test.write(["rep2", "tstamp"], "tstamp\n")
          try:
              # Okay, *this* manipulation accomodates Windows FAT file systems
              # that only have two-second granularity on their timestamps.
              # We round down the current time to the nearest even integer
              # value, subtract two to make sure the timestamp is not "now,"
              # and then convert it back to a float.
              tstamp = float(int(time.time() // 2) * 2) - 2.0
              os.utime(test.workpath("rep2", "tstamp"), (tstamp - 2.0, tstamp))
              f = fs.File("tstamp")
              t = f.get_timestamp()
              assert t == tstamp, "expected %f, got %f" % (tstamp, t)
          finally:
              test.unlink(["rep2", "tstamp"])
  
      def test_get_contents(self):
          """Ensure get_contents() returns binary contents from Repositories"""
          fs = self.fs
          test = self.test
  
          test.write(["rep3", "contents"], "Con\x1aTents\n")
          try:
              c = fs.File("contents").get_contents()
              assert c == bytearray("Con\x1aTents\n", 'utf-8'), "got '%s'" % c
          finally:
              test.unlink(["rep3", "contents"])
  
      def test_get_text_contents(self):
          """Ensure get_text_contents() returns text contents from
          Repositories"""
          fs = self.fs
          test = self.test
  
          # Use a test string that has a file terminator in it to make
          # sure we read the entire file, regardless of its contents.
          test_string = "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):
  
  
  class find_fileTestCase(unittest.TestCase):
      def runTest(self):
          """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')
          test.write(['bar', 'same'], 'bar/same\n')
  
          fs = SCons.Node.FS.FS(test.workpath(""))
          # FS doesn't like the cwd to be something other than its root.
          os.chdir(test.workpath(""))
  
          node_derived = fs.File(test.workpath('bar/baz'))
          node_derived.builder_set(1)  # Any non-zero value.
          node_pseudo = fs.File(test.workpath('pseudo'))
          node_pseudo.set_src_builder(1)  # Any non-zero value.
  
          paths = tuple(map(fs.Dir, ['.', 'same', './bar']))
          nodes = [SCons.Node.FS.find_file('foo', paths),
                   SCons.Node.FS.find_file('baz', paths),
                   SCons.Node.FS.find_file('pseudo', paths),
                   SCons.Node.FS.find_file('same', paths)
          ]
  
          file_names = list(map(str, nodes))
          file_names = list(map(os.path.normpath, file_names))
          expect = ['./foo', './bar/baz', './pseudo', './bar/same']
          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
          # of a directory that we'd otherwise try to search.  If this
          # is broken, we'll see an exception like "Tried to lookup File
          # 'bar/baz' as a Dir.
          SCons.Node.FS.find_file('baz/no_file_here', paths)
  
          import io
          save_sys_stdout = sys.stdout
  
          try:
              sio = io.StringIO()
              sys.stdout = sio
              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 = io.StringIO()
              sys.stdout = sio
              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
  
              sio = io.StringIO()
              sys.stdout = sio
              SCons.Node.FS.find_file('on_disk', paths, verbose=1)
              expect = "  find_file: looking for 'on_disk' in '.' ...\n" + \
                       "  find_file: looking for 'on_disk' in 'same' ...\n" + \
                       "  find_file: looking for 'on_disk' in 'bar' ...\n" + \
                       "  find_file: ... FOUND 'on_disk' in 'bar'\n"
              c = sio.getvalue()
              assert c == expect, c
          finally:
              sys.stdout = save_sys_stdout
  
  
  class StringDirTestCase(unittest.TestCase):
      def runTest(self):
          """Test using a string as the second argument of
          File() and Dir()"""
  
          test = TestCmd(workdir='')
          test.subdir('sub')
          fs = SCons.Node.FS.FS(test.workpath(''))
  
          d = fs.Dir('sub', '.')
          assert str(d) == 'sub', str(d)
          assert d.exists()
          f = fs.File('file', 'sub')
          assert str(f) == os.path.join('sub', 'file')
          assert not f.exists()
  
  
  class stored_infoTestCase(unittest.TestCase):
      def runTest(self):
          """Test how we store build information"""
          test = TestCmd(workdir='')
          test.subdir('sub')
          fs = SCons.Node.FS.FS(test.workpath(''))
  
          d = fs.Dir('sub')
          f = fs.File('file1', d)
          bi = f.get_stored_info()
          assert hasattr(bi, 'ninfo')
  
          class MySConsign:
              class Null:
                  def __init__(self):
                      self.xyzzy = 7
  
              def get_entry(self, name):
                  return self.Null()
  
          def test_sconsign(node):
              return MySConsign()
  
          f = fs.File('file2', d)
          SCons.Node.FS._sconsign_map[2] = test_sconsign
          f.dir._func_sconsign = 2
          bi = f.get_stored_info()
          assert bi.xyzzy == 7, bi
  
  
  class has_src_builderTestCase(unittest.TestCase):
      def runTest(self):
          """Test the has_src_builder() method"""
          test = TestCmd(workdir='')
          fs = SCons.Node.FS.FS(test.workpath(''))
          os.chdir(test.workpath(''))
          test.subdir('sub1')
  
          sub1 = fs.Dir('sub1', '.')
          f1 = fs.File('f1', sub1)
          f2 = fs.File('f2', sub1)
          f3 = fs.File('f3', sub1)
  
          h = f1.has_src_builder()
          assert not h, h
          h = f1.has_builder()
          assert not h, h
  
          b1 = Builder(fs.File)
          sub1.set_src_builder(b1)
  
          test.write(['sub1', 'f2'], "sub1/f2\n")
          h = f1.has_src_builder()  # cached from previous call
          assert not h, h
          h = f1.has_builder()  # cached from previous call
          assert not h, h
          h = f2.has_src_builder()
          assert not h, h
          h = f2.has_builder()
          assert not h, h
          h = f3.has_src_builder()
          assert h, h
          h = f3.has_builder()
          assert h, h
          assert f3.builder is b1, f3.builder
  
  
  class prepareTestCase(unittest.TestCase):
      def runTest(self):
          """Test the prepare() method"""
  
          class MyFile(SCons.Node.FS.File):
              def _createDir(self, update=None):
                  raise SCons.Errors.StopError
  
              def exists(self):
                  return None
  
          fs = SCons.Node.FS.FS()
          file = MyFile('foo', fs.Dir('.'), fs)
  
          exc_caught = 0
          try:
              file.prepare()
          except SCons.Errors.StopError:
              exc_caught = 1
          assert exc_caught, "Should have caught a StopError."
  
          class MkdirAction(Action):
              def __init__(self, dir_made):
                  self.dir_made = dir_made
  
              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 = []
          new_dir = fs.Dir("new_dir")
          new_dir.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
          new_dir.reset_executor()
          xyz = fs.File(os.path.join("new_dir", "xyz"))
  
          xyz.set_state(SCons.Node.up_to_date)
          xyz.prepare()
          assert dir_made == [], dir_made
  
          xyz.set_state(0)
          xyz.prepare()
          assert dir_made[0].get_internal_path() == "new_dir", dir_made[0]
  
          dir = fs.Dir("dir")
          dir.prepare()
  
  @unittest.skipIf(IS_WINDOWS, "No symlinks on windows")
  @unittest.skipUnless(hasattr(os, 'symlink'), "Platform doesn't support symlink")
  class CleanSymlinksTestCase(_tempdirTestCase):
  
      def test_cleans_symlinks(self):
          """Test the prepare() method will cleanup symlinks."""
  
          test = self.test
  
          with open(test.workpath("foo"), "w") as foo:
              foo.write("baz")
  
          os.symlink(test.workpath("foo"), test.workpath("bar"))
          bar = self.fs.File(test.workpath("bar"))
          bar.side_effect = True
          bar.set_state(0)
          assert bar.exists(), "Symlink %s should not exist after prepare"%str(bar)
  
          bar.prepare()
          try:
              os.lstat(test.workpath("bar"))
              assert False, "bar should not exist"
          except FileNotFoundError:
              pass
  
          try:
              os.stat(test.workpath("foo"))
          except FileNotFoundError:
              test.fail('Real file %s should not be removed'%test.workpath('foo'))
  
      def test_cleans_dangling_symlinks(self):
          """Test the prepare() method will cleanup dangling symlinks."""
          test = self.test
  
          with open(test.workpath("foo"), "w") as foo:
              foo.write("baz")
  
          os.symlink(test.workpath("foo"), test.workpath("bar"))
          os.remove(test.workpath("foo"))
          try:
              os.stat(test.workpath("foo"))
              assert False, "foo should not exist"
          except FileNotFoundError:
              pass
  
          bar = self.fs.File(test.workpath("bar"))
          bar.side_effect = True
          bar.set_state(0)
  
          # Dangling links should report not exists
          assert not bar.exists()
  
          bar.prepare()
          try:
              os.lstat(test.workpath("bar"))
              assert False, "bar [%s] should not exist"%test.workpath("bar")
          except FileNotFoundError:
              pass
  
  
  class SConstruct_dirTestCase(unittest.TestCase):
      def runTest(self):
          """Test setting the SConstruct directory"""
  
          fs = SCons.Node.FS.FS()
          fs.set_SConstruct_dir(fs.Dir('xxx'))
          assert fs.SConstruct_dir.get_internal_path() == 'xxx'
  
  
  class CacheDirTestCase(unittest.TestCase):
  
      def test_get_cachedir_csig(self):
          fs = SCons.Node.FS.FS()
  
          f9 = fs.File('f9')
          r = f9.get_cachedir_csig()
          exsig = SCons.Util.MD5signature(SCons.Util.NOFILE)
          assert r == exsig, r
  
  
  class clearTestCase(unittest.TestCase):
      def runTest(self):
          """Test clearing FS nodes of cached data."""
          fs = SCons.Node.FS.FS()
          test = TestCmd(workdir='')
  
          e = fs.Entry('e')
          assert not e.exists()
          assert not e.rexists()
          assert str(e) == 'e', str(e)
          e.clear()
          assert not e.exists()
          assert not e.rexists()
          assert str(e) == 'e', str(e)
  
          d = fs.Dir(test.workpath('d'))
          test.subdir('d')
          assert d.exists()
          assert d.rexists()
          assert str(d) == test.workpath('d'), str(d)
          fs.rename(test.workpath('d'), test.workpath('gone'))
          # Verify caching is active
          assert d.exists(), 'caching not active'
          assert d.rexists()
          assert str(d) == test.workpath('d'), str(d)
          # Now verify clear() resets the cache
          d.clear()
          assert not d.exists()
          assert not d.rexists()
          assert str(d) == test.workpath('d'), str(d)
  
          f = fs.File(test.workpath('f'))
          test.write(test.workpath('f'), 'file f')
          assert f.exists()
          assert f.rexists()
          assert str(f) == test.workpath('f'), str(f)
          # Verify caching is active
          test.unlink(test.workpath('f'))
          assert f.exists()
          assert f.rexists()
          assert str(f) == test.workpath('f'), str(f)
          # Now verify clear() resets the cache
          f.clear()
          assert not f.exists()
          assert not f.rexists()
          assert str(f) == test.workpath('f'), str(f)
          # Now verify clear() resets optional File-specific attributes
          optional_attrs = ['cachedir_csig', 'cachesig', 'contentsig']
          for attr in optional_attrs:
              setattr(f, attr, 'xyz')
          f.clear()
          for attr in optional_attrs:
              assert not hasattr(f, attr), attr
  
  
  class disambiguateTestCase(unittest.TestCase):
      def runTest(self):
          """Test calling the disambiguate() method."""
          test = TestCmd(workdir='')
  
          fs = SCons.Node.FS.FS()
  
          ddd = fs.Dir('ddd')
          d = ddd.disambiguate()
          assert d is ddd, d
  
          fff = fs.File('fff')
          f = fff.disambiguate()
          assert f is fff, f
  
          test.subdir('edir')
          test.write('efile', "efile\n")
  
          edir = fs.Entry(test.workpath('edir'))
          d = edir.disambiguate()
          assert d.__class__ is ddd.__class__, d.__class__
  
          efile = fs.Entry(test.workpath('efile'))
          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."""
          fs = SCons.Node.FS.FS()
  
          e = fs.Entry('e')
          e.postprocess()
  
          d = fs.Dir('d')
          d.postprocess()
  
          f = fs.File('f')
          f.postprocess()
  
  
  class SpecialAttrTestCase(unittest.TestCase):
      def runTest(self):
          """Test special attributes of file nodes."""
          test = TestCmd(workdir='')
          fs = SCons.Node.FS.FS(test.workpath('work'))
  
          f = fs.Entry('foo/bar/baz.blat').get_subst_proxy()
  
          s = str(f.dir)
          assert s == os.path.normpath('foo/bar'), s
          assert f.dir.is_literal(), f.dir
          for_sig = f.dir.for_signature()
          assert for_sig == 'bar', for_sig
  
          s = str(f.file)
          assert s == 'baz.blat', s
          assert f.file.is_literal(), f.file
          for_sig = f.file.for_signature()
          assert for_sig == 'baz.blat_file', for_sig
  
          s = str(f.base)
          assert s == os.path.normpath('foo/bar/baz'), s
          assert f.base.is_literal(), f.base
          for_sig = f.base.for_signature()
          assert for_sig == 'baz.blat_base', for_sig
  
          s = str(f.filebase)
          assert s == 'baz', s
          assert f.filebase.is_literal(), f.filebase
          for_sig = f.filebase.for_signature()
          assert for_sig == 'baz.blat_filebase', for_sig
  
          s = str(f.suffix)
          assert s == '.blat', s
          assert f.suffix.is_literal(), f.suffix
          for_sig = f.suffix.for_signature()
          assert for_sig == 'baz.blat_suffix', for_sig
  
          s = str(f.get_abspath())
          assert s == test.workpath('work', 'foo', 'bar', 'baz.blat'), s
          assert f.abspath.is_literal(), f.abspath
          for_sig = f.abspath.for_signature()
          assert for_sig == 'baz.blat_abspath', for_sig
  
          s = str(f.posix)
          assert s == 'foo/bar/baz.blat', s
          assert f.posix.is_literal(), f.posix
          if f.posix != f:
              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_windows', for_sig
  
          # And now, combinations!!!
          s = str(f.srcpath.base)
          assert s == os.path.normpath('foo/bar/baz'), s
          s = str(f.srcpath.dir)
          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 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.File), g.__class__
  
          s = str(f.srcdir)
          assert s == os.path.normpath('baz/bar'), s
          assert f.srcdir.is_literal(), f.srcdir
          g = f.srcdir.get()
          assert isinstance(g, SCons.Node.FS.Dir), g.__class__
  
          # And now what happens with VariantDir() + Repository()
          fs.Repository(test.workpath('repository'))
  
          f = fs.Entry('foo/sub/file.suffix').get_subst_proxy()
          test.subdir('repository',
                      ['repository', 'baz'],
                      ['repository', 'baz', 'sub'])
  
          rd = test.workpath('repository', 'baz', 'sub')
          rf = test.workpath('repository', 'baz', 'sub', 'file.suffix')
          test.write(rf, "\n")
  
          s = str(f.srcpath)
          assert s == os.path.normpath('baz/sub/file.suffix'), s
          assert f.srcpath.is_literal(), f.srcpath
          g = f.srcpath.get()
          # 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
          assert f.srcdir.is_literal(), f.srcdir
          g = f.srcdir.get()
          assert isinstance(g, SCons.Node.FS.Dir), g.__class__
  
          s = str(f.rsrcpath)
          assert s == rf, s
          assert f.rsrcpath.is_literal(), f.rsrcpath
          g = f.rsrcpath.get()
          assert isinstance(g, SCons.Node.FS.File), g.__class__
  
          s = str(f.rsrcdir)
          assert s == rd, s
          assert f.rsrcdir.is_literal(), f.rsrcdir
          g = f.rsrcdir.get()
          assert isinstance(g, SCons.Node.FS.Dir), g.__class__
  
          # Check that attempts to access non-existent attributes of the
          # subst proxy generate the right exceptions and messages.
          caught = None
          try:
              fs.Dir('ddd').get_subst_proxy().no_such_attr
          except AttributeError as e:
              assert str(e) == "Dir instance 'ddd' has no attribute 'no_such_attr'", e
              caught = 1
          assert caught, "did not catch expected AttributeError"
  
          caught = None
          try:
              fs.Entry('eee').get_subst_proxy().no_such_attr
          except AttributeError as 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"
  
          caught = None
          try:
              fs.File('fff').get_subst_proxy().no_such_attr
          except AttributeError as e:
              assert str(e) == "File instance 'fff' has no attribute 'no_such_attr'", e
              caught = 1
          assert caught, "did not catch expected AttributeError"
  
  
  class SaveStringsTestCase(unittest.TestCase):
      def runTest(self):
          """Test caching string values of nodes."""
          test = TestCmd(workdir='')
  
          def setup(fs):
              fs.Dir('src')
              fs.Dir('d0')
              fs.Dir('d1')
  
              d0_f = fs.File('d0/f')
              d1_f = fs.File('d1/f')
              d0_b = fs.File('d0/b')
              d1_b = fs.File('d1/b')
              d1_f.duplicate = 1
              d1_b.duplicate = 1
              d0_b.builder = 1
              d1_b.builder = 1
  
              return [d0_f, d1_f, d0_b, d1_b]
  
          def modify(nodes):
              d0_f, d1_f, d0_b, d1_b = nodes
              d1_f.duplicate = 0
              d1_b.duplicate = 0
              d0_b.builder = 0
              d1_b.builder = 0
  
          fs1 = SCons.Node.FS.FS(test.workpath('fs1'))
          nodes = setup(fs1)
          fs1.VariantDir('d0', 'src', duplicate=0)
          fs1.VariantDir('d1', 'src', duplicate=1)
  
          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 = 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.VariantDir('d0', 'src', duplicate=0)
          fs2.VariantDir('d1', 'src', duplicate=1)
  
          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 = 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__":
      unittest.main()
  
  # Local Variables:
  # tab-width:4
  # indent-tabs-mode:nil
  # End:
  # vim: set expandtab tabstop=4 shiftwidth=4:
  