Fix errors when there are dangling symlinks. (Gary Oberbrunner)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 8 Jul 2004 16:25:24 +0000 (16:25 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 8 Jul 2004 16:25:24 +0000 (16:25 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@999 fdb21ef1-2011-0410-befe-b5e4ea1792b1

etc/TestCmd.py
etc/TestCommon.py
src/CHANGES.txt
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
test/symlink.py [new file with mode: 0644]

index ca89ed948f03f70340288c6457951e2b492eecdd..48aa2048050fe76a19cbbadc951e620beb5d4b7c 100644 (file)
@@ -88,6 +88,8 @@ things.  Here is an overview of them:
     test.stderr()
     test.stderr(run)
 
+    test.symlink(target, link)
+
     test.match(actual, expected)
 
     test.match_exact("actual 1\nactual 2\n", "expected 1\nexpected 2\n")
@@ -173,8 +175,8 @@ version.
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 __author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.6.D001 2004/03/20 17:39:42 knight"
-__version__ = "0.6"
+__revision__ = "TestCmd.py 0.7.D001 2004/07/08 10:02:13 knight"
+__version__ = "0.7"
 
 import os
 import os.path
@@ -494,6 +496,8 @@ class TestCmd:
         """
         if not self._dirlist:
             return
+        os.chdir(self._cwd)            
+        self.workdir = None
         if condition is None:
             condition = self.condition
         if self._preserve[condition]:
@@ -507,8 +511,6 @@ class TestCmd:
                 shutil.rmtree(dir, ignore_errors = 1)
             self._dirlist = []
                 
-        self.workdir = None
-        os.chdir(self._cwd)            
         try:
             global _Cleanup
             _Cleanup.remove(self)
@@ -752,7 +754,21 @@ class TestCmd:
                 count = count + 1
         return count
 
-    def unlink (self, file):
+    def symlink(self, target, link):
+        """Creates a symlink to the specified target.
+        The link name may be a list, in which case the elements are
+        concatenated with the os.path.join() method.  The link is
+        assumed to be under the temporary working directory unless it
+        is an absolute path name. The target is *not* assumed to be
+        under the temporary working directory.
+        """
+        if is_List(link):
+            link = apply(os.path.join, tuple(link))
+        if not os.path.isabs(link):
+            link = os.path.join(self.workdir, link)
+        os.symlink(target, link)
+
+    def unlink(self, file):
         """Unlinks the specified file name.
         The file name may be a list, in which case the elements are
         concatenated with the os.path.join() method.  The file is
index bf84ed39828b5610bff2f9a2640767c4f32fa189..8f4f22c3d5c65d3183ee7fdb5989d96a2c0774df 100644 (file)
@@ -32,6 +32,8 @@ TestCommon object; see the TestCmd documentation for details.
 Here is an overview of the methods and keyword arguments that are
 provided by the TestCommon class:
 
+    test.must_contain('file', 'required text\n')
+
     test.must_exist('file1', ['file2', ...])
 
     test.must_match('file', "expected contents\n")
@@ -73,8 +75,8 @@ The TestCommon module also provides the following variables
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 __author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCommon.py 0.6.D002 2004/03/29 06:21:41 knight"
-__version__ = "0.6"
+__revision__ = "TestCommon.py 0.7.D001 2004/07/08 10:02:13 knight"
+__version__ = "0.7"
 
 import os
 import os.path
@@ -182,6 +184,19 @@ class TestCommon(TestCmd):
         apply(TestCmd.__init__, [self], kw)
         os.chdir(self.workdir)
 
+    def must_contain(self, file, required):
+        """Ensures that the specified file contains the required text.
+        """
+        file_contents = self.read(file)
+        contains = (string.find(file_contents, required) != -1)
+        if not contains:
+            print "File `%s' does not contain required string." % file
+            print "Required string ====="
+            print required
+            print "%s contents =====" % file
+            print file_contents
+            self.fail_test(not contains)
+
     def must_exist(self, *files):
         """Ensures that the specified file(s) must exist.  An individual
         file be specified as a list of directory names, in which case the
index f2ee12dcdfd8339fac1f4af2bd18175b25ee4db1..0ecb5269583ed4732bd9a3a3746ff18001a9e943 100644 (file)
@@ -157,6 +157,8 @@ RELEASE 0.96 - XXX
   - Add support for fetching command-line keyword=value arguments in
     order from an ARGLIST list.
 
+  - Avoid stack traces when trying to read dangling symlinks.
+
   From Simon Perkins:
 
   - Fix a bug introduced in building shared libraries under MinGW.
index 22d69c055f0ff7be9c2db03a898c276284909322..e61878baa63f3184aca037b2f38bcc6e3a9bfe5c 100644 (file)
@@ -494,7 +494,7 @@ class Base(SCons.Node.Node):
         try:
             return self._exists
         except AttributeError:
-            self._exists = self.fs.exists_or_islink(self.abspath)
+            self._exists = self.fs.exists(self.abspath)
             return self._exists
 
     def rexists(self):
@@ -635,6 +635,8 @@ class Entry(Base):
             self.__class__ = Dir
             self._morph()
             return Dir.get_contents(self)
+        if self.fs.islink(self.abspath):
+            return ''             # avoid errors for dangling symlinks
         raise AttributeError
 
     def exists(self):
@@ -719,9 +721,13 @@ class LocalFS:
         return os.unlink(path)
 
     if hasattr(os, 'symlink'):
+        def islink(self, path):
+            return os.path.islink(path)
         def exists_or_islink(self, path):
             return os.path.exists(path) or os.path.islink(path)
     else:
+        def islink(self, path):
+            return 0                    # no symlinks
         exists_or_islink = exists
 
 #class RemoteFS:
@@ -1700,7 +1706,11 @@ class File(Base):
         else:
             old = BuildInfo()
 
-        mtime = self.get_timestamp()
+        try:
+            mtime = self.get_timestamp()
+        except:
+            mtime = 0
+            raise SCons.Errors.UserError, "no such %s" % self
 
         try:
             if (old.timestamp and old.csig and old.timestamp == mtime):
index bbc64ef9c713d7dcb9ae6b41ae79772d00b07496..231d736972e2d88a6bbe74f1ca25a9afe06c4952 100644 (file)
@@ -990,6 +990,13 @@ class FSTestCase(unittest.TestCase):
         assert c == "", c
         assert e.__class__ == SCons.Node.FS.Dir
 
+        if hasattr(os, 'symlink'):
+            os.symlink('nonexistent', test.workpath('dangling_symlink'))
+            e = fs.Entry('dangling_symlink')
+            c = e.get_contents()
+            assert e.__class__ == SCons.Node.FS.Entry
+            assert c == "", c
+
         test.write("tstamp", "tstamp\n")
         try:
             # Okay, *this* manipulation accomodates Windows FAT file systems
diff --git a/test/symlink.py b/test/symlink.py
new file mode 100644 (file)
index 0000000..62ce75b
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test how we handle symlinks in end-cases.
+"""
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+if not hasattr(os, 'symlink'):
+    print "No os.symlink() method, no symlinks to test."
+    test.no_result(1)
+
+foo_obj = 'foo' + TestSCons._obj
+
+test.write('SConstruct', """
+Program('foo.c')
+""")
+
+test.write('foo.c', """\
+#include "foo.h"
+""")
+
+test.symlink('nonexistent', 'foo.h')
+
+test.run(arguments = '.',
+         status = 2,
+         stderr = None)
+
+expect = "scons: *** [%s] Error 1\n" % foo_obj
+test.fail_test(string.find(test.stderr(), expect) == -1)
+
+test.write('SConstruct', """
+Command('file.out', 'file.in', Copy())
+""")
+
+test.symlink('nonexistent', 'file.in')
+
+test.run(arguments = '.',
+         status = 2,
+         stderr = "scons: *** Source `file.in' not found, needed by target `file.out'.  Stop.\n")
+
+test.pass_test()