Store source file and dependency paths relative to the target's directory, not relati...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 1 Apr 2005 22:58:50 +0000 (22:58 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 1 Apr 2005 22:58:50 +0000 (22:58 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1273 fdb21ef1-2011-0410-befe-b5e4ea1792b1

12 files changed:
bin/restore.sh
src/CHANGES.txt
src/RELEASE.txt
src/engine/SCons/Executor.py
src/engine/SCons/ExecutorTests.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/SConfTests.py
test/sconsign-script.py
test/subdivide.py [new file with mode: 0644]

index 2b7a72663c33ed0bc2360cd9d306c9340e327866..d5dc6c180cc77ed902e53bd661f7d4ec31d9e742 100644 (file)
@@ -5,10 +5,33 @@
 # send in diffs based on the released source.
 #
 
-for i in `find src test -name '*.py'`; do
+if test "X$*" = "X"; then
+    DIRS="src test"
+else
+    DIRS="$*"
+fi
+
+for i in `find $DIRS -name '*.py'`; do
+ed $i <<EOF
+/^__revision__ = /s/= .*/= "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"/p
+/Copyright (c) 2001.*SCons Foundation/s/.*/__COPYRIGHT__/p
+w
+q
+EOF
+done
+
+for i in `find $DIRS -name '*.txt'`; do
+ed $i <<EOF
+/# [^ ]* 0.96.[CD][0-9]* [0-9\/]* [0-9:]* knight$/s/.*/# __FILE__ __REVISION__ __DATE__ __DEVELOPER__/p
+/Copyright (c) 2001.*SCons Foundation/s/.*/__COPYRIGHT__/p
+w
+q
+EOF
+done
+
+for i in `find $DIRS -name '*.xml'`; do
 ed $i <<EOF
-/__revision__ = /s/= .*/= "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"/p
-/# Copyright (c) 2001/s/.*/# __COPYRIGHT__/p
+/^<!-- Copyright (c) 2001.*SCons Foundation -->$/s/.*/<!-- __COPYRIGHT__ -->/p
 w
 q
 EOF
index 555139b2d0dc1191571c6ec8f5c97dfac0ce18d3..779a5d63881d901134b33abdea1bcd3465d44110 100644 (file)
@@ -256,6 +256,12 @@ RELEASE 0.97 - XXX
     to all of the targets in a multiple-target builder call, which
     could cause out-of-order builds when the -j option is used.
 
+  - Store the paths of source files and dependencies in the .sconsign*
+    file(s) relative to the target's directory, not relative to the
+    top-level SConstruct directory.  This starts to make it possible to
+    subdivide the dependency tree arbitrarily by putting an SConstruct
+    file in every directory and using content signatures.
+
   From Wayne Lee:
 
   - Avoid "maximum recursion limit" errors when removing $(-$) pairs
index 095671d63bf7969fbdca824fb5b76ccf71958d8a..9c09e60517052d7e230b39a7e5d2d8da131e9061 100644 (file)
@@ -73,6 +73,23 @@ RELEASE 0.97 - XXX
               the elements.  This may cause rebuilds on Windows systems
               with hierarchical configurations.
 
+          --  STORED DEPENDENCY PATHS ARE NOW RELATIVE TO THE TARGET
+
+              SCons used to store the paths of all source files and
+              dependencies relative to the top-level SConstruct directory.
+              It now stores them relative to the directory of the
+              associated target file.  This makes it possible to use
+              content signatures to subdivide a dependency tree without
+              causing unnecessary rebuilds due to an intermediate file in
+              one build being treated as a source file in a nother build.
+
+              This a step towards making it possible to write a hierarchy
+              of SConstruct files that allow developers to build just
+              one portion of a tree wherever there's an SConstruct file.
+              (Note that this would still require some specific code at
+              the top of each SConstruct file, but we hope to make this
+              an easier/more naturally supported thing in the future.)
+
     --  CACHED Configure() RESULTS ARE STORED IN A DIFFERENT FILE
 
         The Configure() subsystem now stores its cached results in a
index ef460efebecd01af49c3fd070ac3dcb3df989181..afe817a933845c658b5f9286a698363ea45e7b53 100644 (file)
@@ -196,19 +196,16 @@ class Executor:
         """
         return filter(lambda s: s.missing(), self.sources)
 
-    def get_source_binfo(self, calc, ignore=[]):
-        """
-        Return three lists, one of the source files, one of their
-        calculated signatures, and one of their strings (path names).
-        __cacheable__
-        """
+    def get_unignored_sources(self, ignore):
+        """__cacheable__"""
         sourcelist = self.sources
         if ignore:
             sourcelist = filter(lambda s, i=ignore: not s in i, sourcelist)
-        calc_signature = lambda node, calc=calc: node.calc_signature(calc)
-        return (sourcelist,
-                map(calc_signature, sourcelist),
-                map(str,  sourcelist))
+        return sourcelist
+
+    def process_sources(self, func, ignore=[]):
+        """__cacheable__"""
+        return map(func, self.get_unignored_sources(ignore))
 
 
 
@@ -235,8 +232,10 @@ class Null:
         pass
     def get_missing_sources(self):
         return []
-    def get_source_binfo(self, calc, ignore=[]):
-        return ([], [], [])
+    def get_unignored_sources(self, ignore=[]):
+        return []
+    def process_sources(self, func, ignore=[]):
+        return []
 
 
 
index 62c1eab854a32bbe44953a9dc3261b8e3e8418d5..9ff30e58a6a6b79af5e49c1b6d7dcc94ca183b08 100644 (file)
@@ -358,28 +358,41 @@ class ExecutorTestCase(unittest.TestCase):
         missing = x.get_missing_sources()
         assert missing == [sources[0]], missing
 
-    def test_get_source_binfo(self):
-        """Test fetching the build signature info for the sources"""
+    def test_get_unignored_sources(self):
+        """Test fetching the unignored source list"""
         env = MyEnvironment()
-        t1 = MyNode('t')
         s1 = MyNode('s1')
         s2 = MyNode('s2')
-        x = SCons.Executor.Executor('b', env, [{}], [t1], [s1, s2])
-
-        b = x.get_source_binfo('C')
-        assert b == ([s1, s2],
-                     ['cs-C-s1', 'cs-C-s2'],
-                     ['s1', 's2']), b
-
-        b = x.get_source_binfo('C', [s1])
-        assert b == ([s2],
-                     ['cs-C-s2'],
-                     ['s2']), b
-
-        b = x.get_source_binfo('C', [s2])
-        assert b == ([s1],
-                     ['cs-C-s1'],
-                     ['s1']), b
+        s3 = MyNode('s3')
+        x = SCons.Executor.Executor('b', env, [{}], [], [s1, s2, s3])
+
+        r = x.get_unignored_sources([])
+        assert r == [s1, s2, s3], map(str, r)
+
+        r = x.get_unignored_sources([s2])
+        assert r == [s1, s3], map(str, r)
+
+        r = x.get_unignored_sources([s1, s3])
+        assert r == [s2], map(str, r)
+
+    def test_process_sources(self):
+        """Test processing the source list through a function"""
+        env = MyEnvironment()
+        s1 = MyNode('s1')
+        s2 = MyNode('s2')
+        s3 = MyNode('s3')
+        x = SCons.Executor.Executor('b', env, [{}], [], [s1, s2, s3])
+
+        r = x.process_sources(str)
+        assert r == ['s1', 's2', 's3'], r
+
+        r = x.process_sources(str, [s2])
+        assert r == ['s1', 's3'], r
+
+        def xxx(x):
+            return 'xxx-' + str(x)
+        r = x.process_sources(xxx, [s1, s3])
+        assert r == ['xxx-s2'], r
 
 
 
index d2bce26ed9c369e05c68eca93f134fe45cff5bf8..fe014f82a7fc3d7ad7805ff0a72bc515a39728a7 100644 (file)
@@ -297,6 +297,7 @@ class ParentOfRoot:
         self.duplicate=0
         self.srcdir=None
         self.build_dirs=[]
+        self.path_elements=[]
         
     def is_under(self, dir):
         return 0
@@ -461,6 +462,7 @@ class Base(SCons.Node.Node):
             self.path = name
         else:
             self.path = directory.entry_path(name)
+        self.path_elements = directory.path_elements + [self]
 
         self.dir = directory
         self.cwd = None # will hold the SConscript directory for target nodes
@@ -541,18 +543,15 @@ class Base(SCons.Node.Node):
         Node.FS.Base object that owns us."""
         if not dir:
             dir = self.fs.getcwd()
-        path_elems = []
-        d = self
-        if d == dir:
-            path_elems.append('.')
-        else:
-            while d != dir and not isinstance(d, ParentOfRoot):
-                path_elems.append(d.name)
-                d = d.dir
-            path_elems.reverse()
-        ret = string.join(path_elems, os.sep)
-        return ret
-            
+        if self == dir:
+            return '.'
+        path_elems = self.path_elements
+        try: i = path_elems.index(dir)
+        except ValueError: pass
+        else: path_elems = path_elems[i+1:]
+        path_elems = map(lambda n: n.name, path_elems)
+        return string.join(path_elems, os.sep)
+
     def set_src_builder(self, builder):
         """Set the source code builder for this node."""
         self.sbuilder = builder
@@ -643,6 +642,9 @@ class Entry(Base):
             return ''             # avoid errors for dangling symlinks
         raise AttributeError
 
+    def rel_path(self, other):
+        return self.disambiguate().rel_path(other)
+
     def exists(self):
         """Return if the Entry exists.  Check the file system to see
         what we should turn into first.  Assume a file if there's no
@@ -1252,6 +1254,29 @@ class Dir(Base):
         else:
             return self.entries['..'].root()
 
+    def rel_path(self, other):
+        """Return a path to "other" relative to this directory."""
+        if isinstance(other, Dir):
+            name = []
+        else:
+            try:
+                name = [other.name]
+                other = other.dir
+            except AttributeError:
+                return str(other)
+        if self is other:
+            return name and name[0] or '.'
+        i = 0
+        for x, y in map(None, self.path_elements, other.path_elements):
+            if not x is y:
+                break
+            i = i + 1
+        path_elems = ['..']*(len(self.path_elements)-i) \
+                   + map(lambda n: n.name, other.path_elements[i:]) \
+                   + name
+             
+        return string.join(path_elems, os.sep)
+
     def scan(self):
         if not self.implicit is None:
             return
@@ -1596,10 +1621,12 @@ class File(Base):
 
     def get_stored_implicit(self):
         binfo = self.get_stored_info()
-        try:
-            return binfo.bimplicit
-        except AttributeError:
-            return None
+        try: implicit = binfo.bimplicit
+        except AttributeError: return None
+        else: return map(self.dir.Entry, implicit)
+
+    def rel_path(self, other):
+        return self.dir.rel_path(other)
 
     def get_found_includes(self, env, scanner, path):
         """Return the included implicit dependencies in this file.
index adef880dca12b83715bc37f9174b319f3c5588fe..94e9790f22c4e845845f87f3774fa6ee79e4bd62 100644 (file)
@@ -605,15 +605,16 @@ class BuildDirTestCase(unittest.TestCase):
 
         self.failIf(errors)
 
-class FSTestCase(unittest.TestCase):
-    def runTest(self):
+class FSTestCase(_tempdirTestCase):
+    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 = TestCmd(workdir = '')
+        test = self.test
+
         test.subdir('sub', ['sub', 'dir'])
 
         wp = test.workpath('')
@@ -1184,6 +1185,74 @@ class FSTestCase(unittest.TestCase):
         t = z.target_from_source('pre-', '-suf', lambda x: x[:-1])
         assert str(t) == 'pre-z-suf', str(t)
 
+    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',
+        ]
+
+        d1.rel_path(d3)
+
+        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
+
 class DirTestCase(_tempdirTestCase):
 
     def test_entry_exists_on_disk(self):
@@ -1321,7 +1390,7 @@ class DirTestCase(_tempdirTestCase):
         exists_e.exists = return_true
 
         def check(result, expect):
-           result = map(str, result)
+            result = map(str, result)
             expect = map(os.path.normpath, expect)
             assert result == expect, result
 
@@ -2531,7 +2600,6 @@ class SaveStringsTestCase(unittest.TestCase):
 
 if __name__ == "__main__":
     suite = unittest.TestSuite()
-    suite.addTest(FSTestCase())
     suite.addTest(BuildDirTestCase())
     suite.addTest(EntryTestCase())
     suite.addTest(find_fileTestCase())
@@ -2547,6 +2615,7 @@ if __name__ == "__main__":
     suite.addTest(SpecialAttrTestCase())
     suite.addTest(SaveStringsTestCase())
     tclasses = [
+        FSTestCase,
         DirTestCase,
         RepositoryTestCase,
     ]
index a8bb2fafcf7adecc0b521ae32503ea73b5941838..5f03377ea9ad2e207cc4f252e11800f4bcbc2c2d 100644 (file)
@@ -560,6 +560,15 @@ class NodeTestCase(unittest.TestCase):
         assert hasattr(binfo, 'bimplicitsigs')
         assert binfo.bsig == 666, binfo.bsig
 
+    def test_rel_path(self):
+        """Test the rel_path() method
+        """
+        node = SCons.Node.Node()
+        other = SCons.Node.Node()
+        other.__str__ = lambda: "xyzzy"
+        r = node.rel_path(other)
+        assert r == "xyzzy", r
+
     def test_explain(self):
         """Test explaining why a Node must be rebuilt
         """
@@ -578,11 +587,12 @@ class NodeTestCase(unittest.TestCase):
         result = node.explain()
         assert result == None, result
 
-        class Null_BInfo:
-            def __init__(self):
+        def get_null_info():
+            class Null_BInfo:
                 pass
+            return Null_BInfo()
 
-        node.get_stored_info = Null_BInfo
+        node.get_stored_info = get_null_info
         #see above: node.__str__ = lambda: 'null_binfo'
         result = node.explain()
         assert result == "Cannot explain why `null_binfo' is being rebuilt: No previous build information found\n", result
index 8cac2c833d82631f2992adc776c0a54a8892020e..db80985d5ed921f1cb77a3ac61a7491960ec0493 100644 (file)
@@ -480,20 +480,19 @@ class Node:
         # Here's where we implement --implicit-cache.
         if implicit_cache and not implicit_deps_changed:
             implicit = self.get_stored_implicit()
-            if implicit is not None:
+            if implicit:
                 implicit = map(self.implicit_factory, implicit)
                 self._add_child(self.implicit, self.implicit_dict, implicit)
                 calc = build_env.get_calculator()
                 if implicit_deps_unchanged or self.current(calc):
                     return
-                else:
-                    # one of this node's sources has changed, so
-                    # we need to recalculate the implicit deps,
-                    # and the bsig:
-                    self.implicit = []
-                    self.implicit_dict = {}
-                    self._children_reset()
-                    self.del_binfo()
+                # one of this node's sources has changed, so
+                # we need to recalculate the implicit deps,
+                # and the bsig:
+                self.implicit = []
+                self.implicit_dict = {}
+                self._children_reset()
+                self.del_binfo()
 
         executor = self.get_executor()
 
@@ -581,8 +580,12 @@ class Node:
             self.scan()
 
         executor = self.get_executor()
+        def calc_signature(node, calc=calc):
+            return node.calc_signature(calc)
+
+        bsources = executor.process_sources(self.rel_path, self.ignore)
+        sourcesigs = executor.process_sources(calc_signature, self.ignore)
 
-        sourcelist, sourcesigs, bsources = executor.get_source_binfo(calc, self.ignore)
         depends = self.depends
         implicit = self.implicit or []
 
@@ -590,8 +593,6 @@ class Node:
             depends = filter(self.do_not_ignore, depends)
             implicit = filter(self.do_not_ignore, implicit)
 
-        def calc_signature(node, calc=calc):
-            return node.calc_signature(calc)
         dependsigs = map(calc_signature, depends)
         implicitsigs = map(calc_signature, implicit)
 
@@ -603,8 +604,8 @@ class Node:
             sigs.append(binfo.bactsig)
 
         binfo.bsources = bsources
-        binfo.bdepends = map(str, depends)
-        binfo.bimplicit = map(str, implicit)
+        binfo.bdepends = map(self.rel_path, depends)
+        binfo.bimplicit = map(self.rel_path, implicit)
 
         binfo.bsourcesigs = sourcesigs
         binfo.bdependsigs = dependsigs
@@ -614,6 +615,9 @@ class Node:
 
         return binfo
 
+    def rel_path(self, other):
+        return str(other)
+
     def del_cinfo(self):
         try:
             del self.binfo.csig
@@ -930,53 +934,63 @@ class Node:
                 result[k] = s
 
         try:
-            old_bkids = old.bsources + old.bdepends + old.bimplicit
+            osig = {}
+            dictify(osig, old.bsources, old.bsourcesigs)
+            dictify(osig, old.bdepends, old.bdependsigs)
+            dictify(osig, old.bimplicit, old.bimplicitsigs)
         except AttributeError:
             return "Cannot explain why `%s' is being rebuilt: No previous build information found\n" % self
 
-        osig = {}
-        dictify(osig, old.bsources, old.bsourcesigs)
-        dictify(osig, old.bdepends, old.bdependsigs)
-        dictify(osig, old.bimplicit, old.bimplicitsigs)
-
-        new_bsources = map(str, self.binfo.bsources)
-        new_bdepends = map(str, self.binfo.bdepends)
-        new_bimplicit = map(str, self.binfo.bimplicit)
+        new = self.binfo
 
         nsig = {}
-        dictify(nsig, new_bsources, self.binfo.bsourcesigs)
-        dictify(nsig, new_bdepends, self.binfo.bdependsigs)
-        dictify(nsig, new_bimplicit, self.binfo.bimplicitsigs)
+        dictify(nsig, new.bsources, new.bsourcesigs)
+        dictify(nsig, new.bdepends, new.bdependsigs)
+        dictify(nsig, new.bimplicit, new.bimplicitsigs)
+
+        old_bkids = old.bsources + old.bdepends + old.bimplicit
+        new_bkids = new.bsources + new.bdepends + new.bimplicit
+
+        # The sources and dependencies we'll want to report are all stored
+        # as relative paths to this target's directory, but we want to
+        # report them relative to the top-level SConstruct directory,
+        # so we only print them after running them through this lambda
+        # to turn them into the right relative Node and then return
+        # its string.
+        stringify = lambda s, E=self.dir.Entry: str(E(s))
+
+        lines = []
 
-        new_bkids = new_bsources + new_bdepends + new_bimplicit
-        lines = map(lambda x: "`%s' is no longer a dependency\n" % x,
-                    filter(lambda x, nk=new_bkids: not x in nk, old_bkids))
+        removed = filter(lambda x, nk=new_bkids: not x in nk, old_bkids)
+        if removed:
+            removed = map(stringify, removed)
+            fmt = "`%s' is no longer a dependency\n"
+            lines.extend(map(lambda s, fmt=fmt: fmt % s, removed))
 
         for k in new_bkids:
             if not k in old_bkids:
-                lines.append("`%s' is a new dependency\n" % k)
+                lines.append("`%s' is a new dependency\n" % stringify(k))
             elif osig[k] != nsig[k]:
-                lines.append("`%s' changed\n" % k)
+                lines.append("`%s' changed\n" % stringify(k))
 
         if len(lines) == 0 and old_bkids != new_bkids:
             lines.append("the dependency order changed:\n" +
-                         "%sold: %s\n" % (' '*15, old_bkids) +
-                         "%snew: %s\n" % (' '*15, new_bkids))
+                         "%sold: %s\n" % (' '*15, map(stringify, old_bkids)) +
+                         "%snew: %s\n" % (' '*15, map(stringify, new_bkids)))
 
         if len(lines) == 0:
-            newact, newactsig = self.binfo.bact, self.binfo.bactsig
             def fmt_with_title(title, strlines):
                 lines = string.split(strlines, '\n')
                 sep = '\n' + ' '*(15 + len(title))
                 return ' '*15 + title + string.join(lines, sep) + '\n'
-            if old.bactsig != newactsig:
-                if old.bact == newact:
+            if old.bactsig != new.bactsig:
+                if old.bact == new.bact:
                     lines.append("the contents of the build action changed\n" +
-                                 fmt_with_title('action: ', newact))
+                                 fmt_with_title('action: ', new.bact))
                 else:
                     lines.append("the build action changed:\n" +
                                  fmt_with_title('old: ', old.bact) +
-                                 fmt_with_title('new: ', newact))
+                                 fmt_with_title('new: ', new.bact))
 
         if len(lines) == 0:
             return "rebuilding `%s' for unknown reasons\n" % self
index 3cde7d0f2c1967d5f9d972e9b8b69bf79c99ecff..a5e070060902a232d3cc787fd2e389e7f3049181 100644 (file)
@@ -122,9 +122,9 @@ class SConfTestCase(unittest.TestCase):
         log = self.test.read( self.test.workpath('config.log') )
         expr = re.compile( ".*failed in a previous run and all", re.DOTALL ) 
         firstOcc = expr.match( log )
-        assert firstOcc != None 
+        assert firstOcc != None, log
         secondOcc = expr.match( log, firstOcc.end(0) )
-        assert secondOcc == None
+        assert secondOcc == None, log
 
         # 2.2 test the error caching mechanism (dependencies have changed)
         self._resetSConfState()
@@ -235,6 +235,7 @@ int main() {
             assert not res[1][0] and res[1][1] == ""
         finally:
             sconf.Finish()
+        log = self.test.read( self.test.workpath('config.log') )
 
         # test the caching mechanism
         self._resetSConfState()
@@ -251,9 +252,9 @@ int main() {
         log = self.test.read( self.test.workpath('config.log') )
         expr = re.compile( ".*failed in a previous run and all", re.DOTALL )
         firstOcc = expr.match( log )
-        assert firstOcc != None 
+        assert firstOcc != None, log
         secondOcc = expr.match( log, firstOcc.end(0) )
-        assert secondOcc == None 
+        assert secondOcc == None, log
 
 
     def test_TryAction(self):
index 1c407028fb716b193833b8f8baf5faffe74afa71..7a24aef3c35c68c5a0d4501199993ddf56b45b8b 100644 (file)
@@ -51,7 +51,21 @@ def sort_match(test, lines, expect):
 def re_sep(*args):
     return string.replace(apply(os.path.join, args), '\\', '\\\\')
 
-test = TestSCons.TestSCons(match = TestCmd.match_re)
+
+
+class MyTestSCons(TestSCons.TestSCons):
+    # subclass with a method for running the sconsign script
+    def __init__(self, *args, **kw):
+       apply(TestSCons.TestSCons.__init__, (self,)+args, kw)
+       self.my_kw = {
+            'interpreter' : TestSCons.python,
+           'program' : sconsign,
+       }
+    def run_sconsign(self, *args, **kw):
+        kw.update(self.my_kw)
+        return apply(self.run, args, kw)
+
+test = MyTestSCons(match = TestCmd.match_re)
 
 
 
@@ -96,41 +110,33 @@ test.write(['work1', 'sub2', 'inc2.h'], r"""\
 #define STRING2 "inc2.h"
 """)
 
-test.run(chdir = 'work1', arguments = '--implicit-cache .')
+test.run(chdir = 'work1', arguments = '--debug=stacktrace --implicit-cache .')
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "work1/sub1/.sconsign",
+test.run_sconsign(arguments = "work1/sub1/.sconsign",
          stdout = """\
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-v work1/sub1/.sconsign",
+test.run_sconsign(arguments = "-v work1/sub1/.sconsign",
          stdout = """\
 hello.exe:
     timestamp: None
     bsig: \S+
     csig: None
     implicit:
-        %s: \S+
+        hello.obj: \S+
 hello.obj:
     timestamp: None
     bsig: \S+
     csig: None
     implicit:
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-b -v work1/sub1/.sconsign",
+test.run_sconsign(arguments = "-b -v work1/sub1/.sconsign",
          stdout = """\
 hello.exe:
     bsig: \S+
@@ -138,9 +144,7 @@ hello.obj:
     bsig: \S+
 """)
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-c -v work1/sub1/.sconsign",
+test.run_sconsign(arguments = "-c -v work1/sub1/.sconsign",
          stdout = """\
 hello.exe:
     csig: None
@@ -148,74 +152,57 @@ hello.obj:
     csig: None
 """)
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.obj work1/sub1/.sconsign",
+test.run_sconsign(arguments = "-e hello.obj work1/sub1/.sconsign",
          stdout = """\
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.obj -e hello.exe -e hello.obj work1/sub1/.sconsign",
+test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj work1/sub1/.sconsign",
          stdout = """\
 hello.obj: None \S+ None
-        %s: \S+
+        hello.c: \S+
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.c'),
-       re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
-
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "work1/sub2/.sconsign",
+        hello.c: \S+
+""")
+
+# XXX NOT SURE IF THIS IS RIGHT!
+sub2_inc1_h = re_sep('sub2', 'inc1.h')
+sub2_inc2_h = re_sep('sub2', 'inc2.h')
+
+test.run_sconsign(arguments = "work1/sub2/.sconsign",
          stdout = """\
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-        %s: \S+
-        %s: \S+
-""" % (re_sep('sub2', 'hello.obj'),
-       re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h')))
-
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-i -v work1/sub2/.sconsign",
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
+""")
+
+test.run_sconsign(arguments = "-i -v work1/sub2/.sconsign",
          stdout = """\
 hello.exe:
     implicit:
-        %s: \S+
+        hello.obj: \S+
 hello.obj:
     implicit:
-        %s: \S+
-        %s: \S+
-        %s: \S+
-""" % (re_sep('sub2', 'hello.obj'),
-       re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h')))
-
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.obj work1/sub2/.sconsign work1/sub1/.sconsign",
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
+""")
+
+test.run_sconsign(arguments = "-e hello.obj work1/sub2/.sconsign work1/sub1/.sconsign",
          stdout = """\
 hello.obj: None \S+ None
-        %s: \S+
-        %s: \S+
-        %s: \S+
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
 test.run(chdir = 'work1', arguments = '--clean .')
 
@@ -230,29 +217,23 @@ env2.Program('sub2/hello.c')
 
 time.sleep(1)
 
-test.run(chdir = 'work1', arguments = '. --max-drift=1')
+test.run(chdir = 'work1', arguments = '. --max-drift=1 --debug=stacktrace')
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.exe -e hello.obj work1/sub1/.sconsign",
+test.run_sconsign(arguments = "-e hello.exe -e hello.obj work1/sub1/.sconsign",
          stdout = """\
 hello.exe: None \d+ None
-        %s: \d+
+        hello.obj: \d+
 hello.obj: None \d+ None
-        %s: \d+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \d+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.exe -e hello.obj -r work1/sub1/.sconsign",
+test.run_sconsign(arguments = "-e hello.exe -e hello.obj -r work1/sub1/.sconsign",
          stdout = """\
 hello.exe: None \d+ None
-        %s: \d+
+        hello.obj: \d+
 hello.obj: None \d+ None
-        %s: \d+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \d+
+""")
 
 
 ##############################################################################
@@ -297,36 +278,25 @@ test.write(['work2', 'sub2', 'inc2.h'], r"""\
 
 test.run(chdir = 'work2', arguments = '--implicit-cache .')
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "work2/.sconsign")
+test.run_sconsign(arguments = "work2/.sconsign")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "work2/.sconsign",
+test.run_sconsign(arguments = "work2/.sconsign",
          stdout = """\
 === sub1:
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
+        hello.c: \S+
 === sub2:
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-        %s: \S+
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c'),
-       re_sep('sub2', 'hello.obj'),
-       re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h')))
-
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-v work2/.sconsign",
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
+""")
+
+test.run_sconsign(arguments = "-v work2/.sconsign",
          stdout = """\
 === sub1:
 hello.exe:
@@ -334,38 +304,31 @@ hello.exe:
     bsig: \S+
     csig: None
     implicit:
-        %s: \S+
+        hello.obj: \S+
 hello.obj:
     timestamp: None
     bsig: \S+
     csig: None
     implicit:
-        %s: \S+
+        hello.c: \S+
 === sub2:
 hello.exe:
     timestamp: None
     bsig: \S+
     csig: None
     implicit:
-        %s: \S+
+        hello.obj: \S+
 hello.obj:
     timestamp: None
     bsig: \S+
     csig: None
     implicit:
-        %s: \S+
-        %s: \S+
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c'),
-       re_sep('sub2', 'hello.obj'),
-       re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h')))
-
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-b -v work2/.sconsign",
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
+""")
+
+test.run_sconsign(arguments = "-b -v work2/.sconsign",
          stdout = """\
 === sub1:
 hello.exe:
@@ -379,9 +342,7 @@ hello.obj:
     bsig: \S+
 """)
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-c -v work2/.sconsign",
+test.run_sconsign(arguments = "-c -v work2/.sconsign",
          stdout = """\
 === sub1:
 hello.exe:
@@ -395,82 +356,59 @@ hello.obj:
     csig: None
 """)
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.obj work2/.sconsign",
+test.run_sconsign(arguments = "-e hello.obj work2/.sconsign",
          stdout = """\
 === sub1:
 hello.obj: None \S+ None
-        %s: \S+
+        hello.c: \S+
 === sub2:
 hello.obj: None \S+ None
-        %s: \S+
-        %s: \S+
-        %s: \S+
-""" % (re_sep('sub1', 'hello.c'),
-       re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h')))
-
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.obj -e hello.exe -e hello.obj work2/.sconsign",
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
+""")
+
+test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj work2/.sconsign",
          stdout = """\
 === sub1:
 hello.obj: None \S+ None
-        %s: \S+
+        hello.c: \S+
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
+        hello.c: \S+
 === sub2:
 hello.obj: None \S+ None
-        %s: \S+
-        %s: \S+
-        %s: \S+
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-        %s: \S+
-        %s: \S+
-""" % (re_sep('sub1', 'hello.c'),
-       re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c'),
-       re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h'),
-       re_sep('sub2', 'hello.obj'),
-       re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h')))
-
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-i -v work2/.sconsign",
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
+""")
+
+test.run_sconsign(arguments = "-i -v work2/.sconsign",
          stdout = """\
 === sub1:
 hello.exe:
     implicit:
-        %s: \S+
+        hello.obj: \S+
 hello.obj:
     implicit:
-        %s: \S+
+        hello.c: \S+
 === sub2:
 hello.exe:
     implicit:
-        %s: \S+
+        hello.obj: \S+
 hello.obj:
     implicit:
-        %s: \S+
-        %s: \S+
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c'),
-       re_sep('sub2', 'hello.obj'),
-       re_sep('sub2', 'hello.c'),
-       re_sep('sub2', 'inc1.h'),
-       re_sep('sub2', 'inc2.h')))
+        hello.c: \S+
+        inc1.h: \S+
+        inc2.h: \S+
+""")
 
 test.run(chdir = 'work2', arguments = '--clean .')
 
@@ -493,81 +431,63 @@ expect = """\
 hello.c: \d+ None \d+
 """
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.exe -e hello.obj -d sub1 -f dblite work2/my_sconsign",
+test.run_sconsign(arguments = "-e hello.exe -e hello.obj -d sub1 -f dblite work2/my_sconsign",
          stdout = """\
 === sub1:
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.exe -e hello.obj -d sub1 -f dblite work2/my_sconsign.dblite",
+test.run_sconsign(arguments = "-e hello.exe -e hello.obj -d sub1 -f dblite work2/my_sconsign.dblite",
          stdout = """\
 === sub1:
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.c -e hello.exe -e hello.obj -d sub1 -f dblite work2/my_sconsign",
+test.run_sconsign(arguments = "-e hello.c -e hello.exe -e hello.obj -d sub1 -f dblite work2/my_sconsign",
          stdout = """\
 === sub1:
 hello.c: \d+ None \d+
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.c -e hello.exe -e hello.obj -d sub1 -f dblite work2/my_sconsign.dblite",
+test.run_sconsign(arguments = "-e hello.c -e hello.exe -e hello.obj -d sub1 -f dblite work2/my_sconsign.dblite",
          stdout = """\
 === sub1:
 hello.c: \d+ None \d+
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.c -e hello.exe -e hello.obj -r -d sub1 -f dblite work2/my_sconsign",
+test.run_sconsign(arguments = "-e hello.c -e hello.exe -e hello.obj -r -d sub1 -f dblite work2/my_sconsign",
          stdout = """\
 === sub1:
 hello.c: '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' None \d+
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-e hello.c -e hello.exe -e hello.obj -r -d sub1 -f dblite work2/my_sconsign.dblite",
+test.run_sconsign(arguments = "-e hello.c -e hello.exe -e hello.obj -r -d sub1 -f dblite work2/my_sconsign.dblite",
          stdout = """\
 === sub1:
 hello.c: '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' None \d+
 hello.exe: None \S+ None
-        %s: \S+
+        hello.obj: \S+
 hello.obj: None \S+ None
-        %s: \S+
-""" % (re_sep('sub1', 'hello.obj'),
-       re_sep('sub1', 'hello.c')))
+        hello.c: \S+
+""")
 
 ##############################################################################
 
@@ -575,39 +495,25 @@ test.write('bad1', "bad1\n")
 test.write('bad2.dblite', "bad2.dblite\n")
 test.write('bad3', "bad3\n")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-f dblite no_sconsign",
+test.run_sconsign(arguments = "-f dblite no_sconsign",
          stderr = "sconsign: \[Errno 2\] No such file or directory: 'no_sconsign'\n")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-f dblite bad1",
+test.run_sconsign(arguments = "-f dblite bad1",
          stderr = "sconsign: \[Errno 2\] No such file or directory: 'bad1.dblite'\n")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-f dblite bad1.dblite",
+test.run_sconsign(arguments = "-f dblite bad1.dblite",
          stderr = "sconsign: \[Errno 2\] No such file or directory: 'bad1.dblite'\n")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-f dblite bad2",
+test.run_sconsign(arguments = "-f dblite bad2",
          stderr = "sconsign: ignoring invalid `dblite' file `bad2'\n")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-f dblite bad2.dblite",
+test.run_sconsign(arguments = "-f dblite bad2.dblite",
          stderr = "sconsign: ignoring invalid `dblite' file `bad2.dblite'\n")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-f sconsign no_sconsign",
+test.run_sconsign(arguments = "-f sconsign no_sconsign",
          stderr = "sconsign: \[Errno 2\] No such file or directory: 'no_sconsign'\n")
 
-test.run(interpreter = TestSCons.python,
-         program = sconsign,
-         arguments = "-f sconsign bad3",
+test.run_sconsign(arguments = "-f sconsign bad3",
          stderr = "sconsign: ignoring invalid .sconsign file `bad3'\n")
 
 test.pass_test()
diff --git a/test/subdivide.py b/test/subdivide.py
new file mode 100644 (file)
index 0000000..f6aa4f1
--- /dev/null
@@ -0,0 +1,97 @@
+#!/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__"
+
+"""
+Verify that rebuilds do not occur when TargetSignatures()
+content is used to subdivide a dependency tree.
+"""
+
+import os.path
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+#if os.path.exists('sconsign.py'):
+#    sconsign = 'sconsign.py'
+#elif os.path.exists('sconsign'):
+#    sconsign = 'sconsign'
+#else:
+#    print "Can find neither 'sconsign.py' nor 'sconsign' scripts."
+#    test.no_result(1)
+
+test.subdir('src', ['src', 'sub'])
+
+test.write('SConstruct', """\
+TargetSignatures('content')
+env = Environment()
+env.SConscript('src/SConstruct', exports=['env'])
+env.Object('foo.c')
+""")
+
+test.write(['src', 'SConstruct'], """\
+TargetSignatures('content')
+env = Environment()
+p = env.Program('prog', ['main.c', '../foo%s', 'sub/bar.c'])
+env.Default(p)
+""" % TestSCons._obj)
+
+test.write('foo.c', """\
+void
+foo(void) {
+    printf("foo.c\\n");
+}
+""")
+
+test.write(['src', 'main.c'], """\
+extern void foo(void);
+extern void bar(void);
+int
+main(int argc, char *argv[]) {
+    foo();
+    bar();
+    printf("src/main.c\\n");
+    exit (0);
+}
+""")
+
+test.write(['src', 'sub', 'bar.c'], """\
+void
+bar(void) {
+    printf("bar.c\\n");
+}
+""")
+
+test.run()
+
+test.run(program=test.workpath('src', 'prog'),
+         stdout="foo.c\nbar.c\nsrc/main.c\n")
+
+test.up_to_date(chdir='src', arguments = test.workpath())
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()