Refactor Environment/Executor/Node scanner interaction a little. Put --debug={dtree...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 13 Feb 2005 13:05:22 +0000 (13:05 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 13 Feb 2005 13:05:22 +0000 (13:05 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1227 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Executor.py
src/engine/SCons/ExecutorTests.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/__init__.py
test/option--debug.py
test/option/debug-dtree.py [new file with mode: 0644]
test/option/debug-includes.py [new file with mode: 0644]
test/option/debug-stree.py [new file with mode: 0644]
test/option/debug-tree.py [new file with mode: 0644]

index 013a125898f21ab164fe184fbcce62870c20e451..5b45d5521acaa979e91b543c76049325c263dfc3 100644 (file)
@@ -88,20 +88,24 @@ class Executor:
             cwd = None
         return scanner.path(env, cwd, self.targets, self.sources)
 
+    def get_kw(self, kw={}):
+        result = self.builder_kw.copy()
+        result.update(kw)
+        return result
+
     def do_nothing(self, target, errfunc, kw):
         pass
 
     def do_execute(self, target, errfunc, kw):
         """Actually execute the action list."""
-        kw = kw.copy()
-        kw.update(self.builder_kw)
-        apply(self.action, (self.targets, self.sources,
-                            self.get_build_env(), errfunc), kw)
+        apply(self.action,
+              (self.targets, self.sources, self.get_build_env(), errfunc),
+              self.get_kw(kw))
 
     # use extra indirection because with new-style objects (Python 2.2
     # and above) we can't override special methods, and nullify() needs
     # to be able to do this.
-    
+
     def __call__(self, target, errfunc, **kw):
         self.do_execute(target, errfunc, kw)
 
@@ -117,7 +121,7 @@ class Executor:
         self.sources.extend(slist)
 
     # another extra indirection for new-style objects and nullify...
-    
+
     def my_str(self):
         return self.action.genstring(self.targets,
                                      self.sources,
@@ -163,7 +167,8 @@ class Executor:
         if scanner:
             initial_scanners = lambda src, s=scanner: (src, s)
         else:
-            initial_scanners = lambda src, e=env: (src, e.get_scanner(src.scanner_key()))
+            kw = self.get_kw()
+            initial_scanners = lambda src, e=env, kw=kw: (src, src.get_scanner(e, kw))
         scanner_list = map(initial_scanners, self.sources)
         scanner_list = filter(remove_null_scanners, scanner_list)
         scanner_list = map(select_specific_scanner, scanner_list)
index 189cbad9792009189cdf0da847063a8a3bb761f2..d3af914f4058e8e5e43af866cb1217668170ae94 100644 (file)
@@ -78,8 +78,10 @@ class MyNode:
                                            [self],
                                            ['s1', 's2'])
         apply(executor, (self, errfunc), {})
+    def get_scanner(self, env, kw):
+        return MyScanner('dep-')
     def get_implicit_deps(self, env, scanner, path):
-        return ['dep-' + str(self)]
+        return [scanner.prefix + str(self)]
     def add_to_implicit(self, deps):
         self.implicit.extend(deps)
     def missing(self):
@@ -88,6 +90,8 @@ class MyNode:
         return 'cs-'+calc+'-'+self.name
 
 class MyScanner:
+    def __init__(self, prefix):
+        self.prefix = prefix
     def path(self, env, cwd, target, source):
         return ()
     def select(self, node):
@@ -162,6 +166,22 @@ class ExecutorTestCase(unittest.TestCase):
         p = x.get_build_scanner_path(s)
         assert p == "scanner: sss, here, ['t'], ['s1', 's2']", p
 
+    def test_get_kw(self):
+        """Test the get_kw() method"""
+        t = MyNode('t')
+        x = SCons.Executor.Executor(MyAction(),
+                                    MyEnvironment(),
+                                    [],
+                                    [t],
+                                    ['s1', 's2'],
+                                    builder_kw={'X':1, 'Y':2})
+        kw = x.get_kw()
+        assert kw == {'X':1, 'Y':2}, kw
+        kw = x.get_kw({'Z':3})
+        assert kw == {'X':1, 'Y':2, 'Z':3}, kw
+        kw = x.get_kw({'X':4})
+        assert kw == {'X':4, 'Y':2}, kw
+
     def test__call__(self):
         """Test calling an Executor"""
         result = []
@@ -296,9 +316,16 @@ class ExecutorTestCase(unittest.TestCase):
         targets = [MyNode('t')]
         sources = [MyNode('s1'), MyNode('s2')]
         x = SCons.Executor.Executor('b', env, [{}], targets, sources)
-        scanner = MyScanner()
-        deps = x.scan(scanner)
-        assert targets[0].implicit == ['dep-s1', 'dep-s2'], targets[0].implicit
+
+        deps = x.scan(None)
+        t = targets[0]
+        assert t.implicit == ['dep-s1', 'dep-s2'], t.implicit
+
+        t.implicit = []
+
+        deps = x.scan(MyScanner('scanner-'))
+        t = targets[0]
+        assert t.implicit == ['scanner-s1', 'scanner-s2'], t.implicit
 
     def test_get_missing_sources(self):
         """Test the ability to check if any sources are missing"""
index 0637cebc0f165a8f5f3670b1d8c6a5c7bd07bd77..281b5f21fb77c5b268ed88c5a4fb17e2457bd5bf 100644 (file)
@@ -830,6 +830,17 @@ class NodeTestCase(unittest.TestCase):
         deps = node.get_implicit_deps(env, s, target)
         assert deps == [d, e, f], map(str, deps)
 
+    def test_get_scanner(self):
+        """Test fetching the environment scanner for a Node
+        """
+        node = SCons.Node.Node()
+        scanner = Scanner()
+        env = Environment(SCANNERS = [scanner])
+        s = node.get_scanner(env)
+        assert s == scanner, s
+        s = node.get_scanner(env, {'X':1})
+        assert s == scanner, s
+
     def test_get_source_scanner(self):
         """Test fetching the source scanner for a Node
         """
index c7167a394fe4757ad6e9a41a162576b51666bdb1..96a78cadb64393a212d1d2be6c5114d468db65f7 100644 (file)
@@ -425,6 +425,8 @@ class Node:
         """
         return self.builder.source_factory(path)
 
+    def get_scanner(self, env, kw={}):
+        return env.get_scanner(self.scanner_key())
 
     def get_source_scanner(self, node):
         """Fetch the source scanner for the specified node
@@ -447,7 +449,7 @@ class Node:
             # The builder didn't have an explicit scanner, so go look up
             # a scanner from env['SCANNERS'] based on the node's scanner
             # key (usually the file extension).
-            scanner = self.get_build_env().get_scanner(node.scanner_key())
+            scanner = self.get_scanner(self.get_build_env())
         if scanner:
             scanner = scanner.select(node)
         return scanner
@@ -758,22 +760,7 @@ class Node:
     def do_not_ignore(self, node):
         return node not in self.ignore
 
-    def _children_get(self):
-        "__cacheable__"
-        children = self.all_children(scan=0)
-        if self.ignore:
-            children = filter(self.do_not_ignore, children)
-        return children
-        
-    def children(self, scan=1):
-        """Return a list of the node's direct children, minus those
-        that are ignored by this node."""
-        if scan:
-            self.scan()
-        return self._children_get()
-
-    def all_children(self, scan=1):
-        """Return a list of all the node's direct children."""
+    def _all_children_get(self):
         # The return list may contain duplicate Nodes, especially in
         # source trees where there are a lot of repeated #includes
         # of a tangle of .h files.  Profiling shows, however, that
@@ -791,13 +778,31 @@ class Node:
         # using dictionary keys, lose the order, and the only ordered
         # dictionary patterns I found all ended up using "not in"
         # internally anyway...)
-        if scan:
-            self.scan()
         if self.implicit is None:
             return self.sources + self.depends
         else:
             return self.sources + self.depends + self.implicit
 
+    def _children_get(self):
+        "__cacheable__"
+        children = self._all_children_get()
+        if self.ignore:
+            children = filter(self.do_not_ignore, children)
+        return children
+
+    def all_children(self, scan=1):
+        """Return a list of all the node's direct children."""
+        if scan:
+            self.scan()
+        return self._all_children_get()
+
+    def children(self, scan=1):
+        """Return a list of the node's direct children, minus those
+        that are ignored by this node."""
+        if scan:
+            self.scan()
+        return self._children_get()
+
     def set_state(self, state):
         self.state = state
 
index 322afda114d59fd7e8f5726b3b52349bc9e30300..2ded69a181e6c60580bd8de6b3405452b8defdeb 100644 (file)
@@ -65,154 +65,8 @@ test.write('bar.h', """
 #endif
 """)
 
-test.run(arguments = "--debug=tree foo.xxx")
-
-tree = """
-+-foo.xxx
-  +-foo.ooo
-  | +-foo.c
-  | +-foo.h
-  | +-bar.h
-  +-bar.ooo
-    +-bar.c
-    +-bar.h
-    +-foo.h
-"""
-
-test.fail_test(string.find(test.stdout(), tree) == -1)
-
-test.run(arguments = "--debug=tree foo.xxx")
-test.fail_test(string.find(test.stdout(), tree) == -1)
-
-stree = """
-[E B   C]+-foo.xxx
-[E B   C]  +-foo.ooo
-[E      ]  | +-foo.c
-[E      ]  | +-foo.h
-[E      ]  | +-bar.h
-[E B   C]  +-bar.ooo
-[E      ]    +-bar.c
-[E      ]    +-bar.h
-[E      ]    +-foo.h
-"""
-
-test.run(arguments = "--debug=stree foo.xxx")
-test.fail_test(string.find(test.stdout(), stree) == -1)
-
-stree2 = """
- E       = exists
-  R      = exists in repository only
-   b     = implicit builder
-   B     = explicit builder
-    S    = side effect
-     P   = precious
-      A  = always build
-       C = current
-
-[  B    ]+-foo.xxx
-[  B    ]  +-foo.ooo
-[E      ]  | +-foo.c
-[E      ]  | +-foo.h
-[E      ]  | +-bar.h
-[  B    ]  +-bar.ooo
-[E      ]    +-bar.c
-[E      ]    +-bar.h
-[E      ]    +-foo.h
-"""
-
-test.run(arguments = '-c foo.xxx')
-test.run(arguments = "--no-exec --debug=stree foo.xxx")
-test.fail_test(string.find(test.stdout(), stree2) == -1)
-
-
-
-dtree = """
-+-foo.xxx
-  +-foo.ooo
-  +-bar.ooo
-"""
-
-test.run(arguments = "--debug=dtree foo.xxx")
-test.fail_test(string.find(test.stdout(), dtree) == -1)
-
-includes = """
-+-foo.c
-  +-foo.h
-    +-bar.h
-"""
-test.run(arguments = "--debug=includes foo.ooo")
-test.fail_test(string.find(test.stdout(), includes) == -1)
-
-# Make sure we print the debug stuff even if there's a build failure.
-test.write('bar.h', """
-#ifndef BAR_H
-#define BAR_H
-#include "foo.h"
-#endif
-THIS SHOULD CAUSE A BUILD FAILURE
-""")
-
-test.run(arguments = "--debug=tree foo.xxx",
-         status = 2,
-         stderr = None)
-test.fail_test(string.find(test.stdout(), tree) == -1)
-
-test.run(arguments = "--debug=dtree foo.xxx",
-         status = 2,
-         stderr = None)
-test.fail_test(string.find(test.stdout(), dtree) == -1)
-
-# In an ideal world, --debug=includes would also work when there's a build
-# failure, but this would require even more complicated logic to scan
-# all of the intermediate nodes that get skipped when the build failure
-# occurs.  On the YAGNI theory, we're just not going to worry about this
-# until it becomes an issue that someone actually cares enough about.
-#test.run(arguments = "--debug=includes foo.xxx",
-#         status = 2,
-#         stderr = None)
-#test.fail_test(string.find(test.stdout(), includes) == -1)
-
-# Restore bar.h to something good.
-test.write('bar.h', """
-#ifndef BAR_H
-#define BAR_H
-#include "foo.h"
-#endif
-""")
-
-# These shouldn't print out anything in particular, but
-# they shouldn't crash either:
-test.run(arguments = "--debug=includes .")
-test.run(arguments = "--debug=includes foo.c")
-
-tree = """scons: `.' is up to date.
-
-+-.
-  +-SConstruct
-  +-bar.c
-  +-bar.h
-  +-bar.ooo
-  | +-bar.c
-  | +-bar.h
-  | +-foo.h
-  +-foo.c
-  +-foo.h
-  +-foo.ooo
-  | +-foo.c
-  | +-foo.h
-  | +-bar.h
-  +-foo.xxx
-    +-foo.ooo
-    | +-foo.c
-    | +-foo.h
-    | +-bar.h
-    +-bar.ooo
-      +-bar.c
-      +-bar.h
-      +-foo.h
-"""
-test.run(arguments = "--debug=tree .")
-test.fail_test(string.find(test.stdout(), tree) == -1)
+############################
+# test --debug=time
 
 test.run(arguments = "--debug=pdb", stdin = "n\ns\nq\n")
 test.fail_test(string.find(test.stdout(), "(Pdb)") == -1)
diff --git a/test/option/debug-dtree.py b/test/option/debug-dtree.py
new file mode 100644 (file)
index 0000000..c93ff7f
--- /dev/null
@@ -0,0 +1,107 @@
+#!/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 that the --debug=dtree option correctly prints just the explicit
+dependencies (sources or Depends()) of a target.
+"""
+
+import TestSCons
+import sys
+import string
+import re
+import time
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
+env.Program('foo', Split('foo.c bar.c'))
+""")
+
+test.write('foo.c', r"""
+#include "foo.h"
+int main(int argc, char *argv[])
+{
+       argv[argc++] = "--";
+       printf("f1.c\n");
+       exit (0);
+}
+""")
+
+test.write('bar.c', """
+#include "bar.h"
+""")
+
+test.write('foo.h', """
+#ifndef FOO_H
+#define FOO_H
+#include "bar.h"
+#endif
+""")
+
+test.write('bar.h', """
+#ifndef BAR_H
+#define BAR_H
+#include "foo.h"
+#endif
+""")
+
+dtree1 = """
++-foo.xxx
+  +-foo.ooo
+  +-bar.ooo
+"""
+
+test.run(arguments = "--debug=dtree foo.xxx")
+test.fail_test(string.find(test.stdout(), dtree1) == -1)
+
+dtree2 = """
++-.
+  +-bar.ooo
+  +-foo.ooo
+  +-foo.xxx
+    +-foo.ooo
+    +-bar.ooo
+"""
+test.run(arguments = "--debug=dtree .")
+test.fail_test(string.find(test.stdout(), dtree2) == -1)
+
+# Make sure we print the debug stuff even if there's a build failure.
+test.write('bar.h', """
+#ifndef BAR_H
+#define BAR_H
+#include "foo.h"
+#endif
+THIS SHOULD CAUSE A BUILD FAILURE
+""")
+
+test.run(arguments = "--debug=dtree foo.xxx",
+         status = 2,
+         stderr = None)
+test.fail_test(string.find(test.stdout(), dtree1) == -1)
+
+test.pass_test()
diff --git a/test/option/debug-includes.py b/test/option/debug-includes.py
new file mode 100644 (file)
index 0000000..c232175
--- /dev/null
@@ -0,0 +1,105 @@
+#!/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 that the --debug=includes option prints the implicit
+dependencies of a target.
+"""
+
+import TestSCons
+import sys
+import string
+import re
+import time
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
+env.Program('foo', Split('foo.c bar.c'))
+""")
+
+test.write('foo.c', r"""
+#include "foo.h"
+int main(int argc, char *argv[])
+{
+       argv[argc++] = "--";
+       printf("f1.c\n");
+       exit (0);
+}
+""")
+
+test.write('bar.c', """
+#include "bar.h"
+""")
+
+test.write('foo.h', """
+#ifndef FOO_H
+#define FOO_H
+#include "bar.h"
+#endif
+""")
+
+test.write('bar.h', """
+#ifndef BAR_H
+#define BAR_H
+#include "foo.h"
+#endif
+""")
+
+includes = """
++-foo.c
+  +-foo.h
+    +-bar.h
+"""
+test.run(arguments = "--debug=includes foo.ooo")
+test.fail_test(string.find(test.stdout(), includes) == -1)
+
+# In an ideal world, --debug=includes would also work when there's a build
+# failure, but this would require even more complicated logic to scan
+# all of the intermediate nodes that get skipped when the build failure
+# occurs.  On the YAGNI theory, we're just not going to worry about this
+# until it becomes an issue that someone actually cares enough about.
+
+#test.write('bar.h', """
+##ifndef BAR_H
+##define BAR_H
+##include "foo.h"
+##endif
+#THIS SHOULD CAUSE A BUILD FAILURE
+#""")
+
+#test.run(arguments = "--debug=includes foo.xxx",
+#         status = 2,
+#         stderr = None)
+#test.fail_test(string.find(test.stdout(), includes) == -1)
+
+# These shouldn't print out anything in particular, but
+# they shouldn't crash either:
+test.run(arguments = "--debug=includes .")
+test.run(arguments = "--debug=includes foo.c")
+
+test.pass_test()
diff --git a/test/option/debug-stree.py b/test/option/debug-stree.py
new file mode 100644 (file)
index 0000000..ba18cfb
--- /dev/null
@@ -0,0 +1,114 @@
+#!/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 that the --debug=stree option prints a dependency tree with output
+that indicates the state of various Node status flags.
+"""
+
+import TestSCons
+import sys
+import string
+import re
+import time
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
+env.Program('foo', Split('foo.c bar.c'))
+""")
+
+test.write('foo.c', r"""
+#include "foo.h"
+int main(int argc, char *argv[])
+{
+       argv[argc++] = "--";
+       printf("f1.c\n");
+       exit (0);
+}
+""")
+
+test.write('bar.c', """
+#include "bar.h"
+""")
+
+test.write('foo.h', """
+#ifndef FOO_H
+#define FOO_H
+#include "bar.h"
+#endif
+""")
+
+test.write('bar.h', """
+#ifndef BAR_H
+#define BAR_H
+#include "foo.h"
+#endif
+""")
+
+stree = """
+[E B   C]+-foo.xxx
+[E B   C]  +-foo.ooo
+[E      ]  | +-foo.c
+[E      ]  | +-foo.h
+[E      ]  | +-bar.h
+[E B   C]  +-bar.ooo
+[E      ]    +-bar.c
+[E      ]    +-bar.h
+[E      ]    +-foo.h
+"""
+
+test.run(arguments = "--debug=stree foo.xxx")
+test.fail_test(string.find(test.stdout(), stree) == -1)
+
+stree2 = """
+ E       = exists
+  R      = exists in repository only
+   b     = implicit builder
+   B     = explicit builder
+    S    = side effect
+     P   = precious
+      A  = always build
+       C = current
+
+[  B    ]+-foo.xxx
+[  B    ]  +-foo.ooo
+[E      ]  | +-foo.c
+[E      ]  | +-foo.h
+[E      ]  | +-bar.h
+[  B    ]  +-bar.ooo
+[E      ]    +-bar.c
+[E      ]    +-bar.h
+[E      ]    +-foo.h
+"""
+
+test.run(arguments = '-c foo.xxx')
+
+test.run(arguments = "--no-exec --debug=stree foo.xxx")
+test.fail_test(string.find(test.stdout(), stree2) == -1)
+
+test.pass_test()
diff --git a/test/option/debug-tree.py b/test/option/debug-tree.py
new file mode 100644 (file)
index 0000000..4bb1229
--- /dev/null
@@ -0,0 +1,130 @@
+#!/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 that the --debug=tree option prints a tree representation of the
+complete dependencies of a target.
+"""
+
+import TestSCons
+import sys
+import string
+import re
+import time
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
+env.Program('foo', Split('foo.c bar.c'))
+""")
+
+test.write('foo.c', r"""
+#include "foo.h"
+int main(int argc, char *argv[])
+{
+       argv[argc++] = "--";
+       printf("f1.c\n");
+       exit (0);
+}
+""")
+
+test.write('bar.c', """
+#include "bar.h"
+""")
+
+test.write('foo.h', """
+#ifndef FOO_H
+#define FOO_H
+#include "bar.h"
+#endif
+""")
+
+test.write('bar.h', """
+#ifndef BAR_H
+#define BAR_H
+#include "foo.h"
+#endif
+""")
+
+tree1 = """
++-foo.xxx
+  +-foo.ooo
+  | +-foo.c
+  | +-foo.h
+  | +-bar.h
+  +-bar.ooo
+    +-bar.c
+    +-bar.h
+    +-foo.h
+"""
+
+test.run(arguments = "--debug=tree foo.xxx")
+test.fail_test(string.find(test.stdout(), tree1) == -1)
+
+tree2 = """
++-.
+  +-SConstruct
+  +-bar.c
+  +-bar.h
+  +-bar.ooo
+  | +-bar.c
+  | +-bar.h
+  | +-foo.h
+  +-foo.c
+  +-foo.h
+  +-foo.ooo
+  | +-foo.c
+  | +-foo.h
+  | +-bar.h
+  +-foo.xxx
+    +-foo.ooo
+    | +-foo.c
+    | +-foo.h
+    | +-bar.h
+    +-bar.ooo
+      +-bar.c
+      +-bar.h
+      +-foo.h
+"""
+test.run(arguments = "--debug=tree .")
+test.fail_test(string.find(test.stdout(), tree2) == -1)
+
+# Make sure we print the debug stuff even if there's a build failure.
+test.write('bar.h', """
+#ifndef BAR_H
+#define BAR_H
+#include "foo.h"
+#endif
+THIS SHOULD CAUSE A BUILD FAILURE
+""")
+
+test.run(arguments = "--debug=tree foo.xxx",
+         status = 2,
+         stderr = None)
+test.fail_test(string.find(test.stdout(), tree1) == -1)
+
+test.pass_test()