Improve new post-PathList refactoring performance. (Charles Crain)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 7 May 2003 11:52:31 +0000 (11:52 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 7 May 2003 11:52:31 +0000 (11:52 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@673 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/EnvironmentTests.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/Util.py
src/engine/SCons/UtilTests.py

index befac1e4c88e46c0252b13f21ac4211715dfbd4b..49b0cb11f873e83dbbdc922fc26092c9fd6d2441 100644 (file)
@@ -262,8 +262,10 @@ class CommandAction(ActionBase):
         if not SCons.Util.is_List(cmd):
             cmd = [ cmd ]
         return SCons.Util.scons_subst(string.join(map(str, cmd)),
-                                      env, SCons.Util.SUBST_RAW,
-                                      target, source)
+                                      env,
+                                      SCons.Util.SUBST_RAW,
+                                      SCons.Util.target_prep(target),
+                                      SCons.Util.source_prep(source))
 
     def get_contents(self, target, source, env):
         """Return the signature contents of this action's command line.
@@ -277,7 +279,8 @@ class CommandAction(ActionBase):
         return SCons.Util.scons_subst(string.join(map(str, cmd)),
                                       env,
                                       SCons.Util.SUBST_SIG,
-                                      target, source)
+                                      SCons.Util.target_prep(target),
+                                      SCons.Util.source_prep(source))
 
 class CommandGeneratorAction(ActionBase):
     """Class for command-generator actions."""
@@ -403,6 +406,8 @@ class ListAction(ActionBase):
 
         Simple concatenation of the signatures of the elements.
         """
+        target = SCons.Util.target_prep(target)
+        source = SCons.Util.source_prep(source)
         return string.join(map(lambda x, t=target, s=source, e=env:
                                       x.get_contents(t, s, e),
                                self.list),
index 33bf57e91f20ae1d91999f28b4aa11e561be5338..3e325948e1afc2099b285e27dc1b65cb62c7b6b5 100644 (file)
@@ -134,6 +134,8 @@ class DummyNode:
         return self.name
     def rfile(self):
         return self
+    def get_subst_proxy(self):
+        return self
 
 if os.name == 'java':
     python = os.path.join(sys.prefix, 'jython')
@@ -392,7 +394,7 @@ class CommandActionTestCase(unittest.TestCase):
         cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd2)
-        r = act('foo', [], env.Copy())
+        r = act(DummyNode('foo'), [], env.Copy())
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: 'foo'\n", c
@@ -400,7 +402,7 @@ class CommandActionTestCase(unittest.TestCase):
         cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
 
         act = SCons.Action.CommandAction(cmd3)
-        r = act(['aaa', 'bbb'], [], env.Copy())
+        r = act(map(DummyNode, ['aaa', 'bbb']), [], env.Copy())
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: 'aaa' 'bbb'\n", c
@@ -437,13 +439,13 @@ class CommandActionTestCase(unittest.TestCase):
             PATH = ''
         
         env5['ENV']['XYZZY'] = 'xyzzy'
-        r = act(target = 'out5', source = [], env = env5)
+        r = act(target = DummyNode('out5'), source = [], env = env5)
 
         act = SCons.Action.CommandAction(cmd5)
         r = act(target = DummyNode('out5'),
-                        source = [],
-                        env = env.Copy(ENV = {'XYZZY' : 'xyzzy',
-                                              'PATH' : PATH}))
+                source = [],
+                env = env.Copy(ENV = {'XYZZY' : 'xyzzy',
+                                      'PATH' : PATH}))
         assert r == 0
         c = test.read(outfile, 'r')
         assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c
@@ -455,6 +457,8 @@ class CommandActionTestCase(unittest.TestCase):
                 return self._str
             def rfile(self):
                 return self
+            def get_subst_proxy(self):
+                return self
 
         cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile)
 
@@ -749,6 +753,8 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
             def rfile(self):
                 self.t.rfile_called = 1
                 return self
+            def get_subst_proxy(self):
+                return self
         def f3(target, source, env, for_signature):
             return ''
         c = SCons.Action.CommandGeneratorAction(f3)
index 5f9a18ca145c2631d218892b09f736874417662a..6def6d57eef11fd9c3c83fcc4c2d13da5e87507a 100644 (file)
@@ -255,6 +255,8 @@ class BuilderTestCase(unittest.TestCase):
                 return self.name
             def rfile(self):
                 return self
+            def get_subst_proxy(self):
+                return self
 
         target = map(DummyNode, map(lambda x: "__t%d__" % x, range(1, 7)))
         source = map(DummyNode, map(lambda x: "__s%d__" % x, range(1, 7)))
index 28ea0e585b68767a9fb50488ebe6d89d7bdf3d62..6473bddf5258744ad846c9c5f3f2e3ff7e16ec78 100644 (file)
@@ -574,6 +574,8 @@ class EnvironmentTestCase(unittest.TestCase):
                 return self.name
             def rfile(self):
                 return self
+            def get_subst_proxy(self):
+                return self
 
         # Test callables in the Environment
         def foo(target, source, env, for_signature):
index e64aacca90f8a42c127ebc17cdd5b6118c178171..884736ea139f0ee7baa88caca599384085a7bcc2 100644 (file)
@@ -190,7 +190,7 @@ class ParentOfRoot:
     This class is an instance of the Null object pattern.
     """
     def __init__(self):
-        self.abspath_str = ''
+        self.abspath = ''
         self.path = ''
         self.abspath_ = ''
         self.path_ = ''
@@ -224,6 +224,61 @@ else:
     def _my_normcase(x):
         return string.upper(x)
 
+class EntryProxy(SCons.Util.Proxy):
+    def __init__(self, entry):
+        SCons.Util.Proxy.__init__(self, entry)
+        self.abspath = SCons.Util.SpecialAttrWrapper(entry.abspath,
+                                                     entry.name + "_abspath")
+        filebase, suffix = os.path.splitext(entry.name)
+        self.filebase = SCons.Util.SpecialAttrWrapper(filebase,
+                                                      entry.name + "_filebase")
+        self.suffix = SCons.Util.SpecialAttrWrapper(suffix,
+                                                    entry.name + "_suffix")
+        self.file = SCons.Util.SpecialAttrWrapper(entry.name,
+                                                  entry.name + "_file")
+
+    def __get_base_path(self):
+        """Return the file's directory and file name, with the
+        suffix stripped."""
+        return SCons.Util.SpecialAttrWrapper(os.path.splitext(self.get().get_path())[0],
+                                             self.get().name + "_base")
+
+    def __get_posix_path(self):
+        """Return the path with / as the path separator, regardless
+        of platform."""
+        if os.sep == '/':
+            return self
+        else:
+            return SCons.Util.SpecialAttrWrapper(string.replace(self.get().get_path(),
+                                                                os.sep, '/'),
+                                                 self.get().name + "_posix")
+
+    def __get_srcnode(self):
+        return EntryProxy(self.get().srcnode())
+
+    def __get_srcdir(self):
+        """Returns the directory containing the source node linked to this
+        node via BuildDir(), or the directory of this node if not linked."""
+        return EntryProxy(self.get().srcnode().dir)
+
+    def __get_dir(self):
+        return EntryProxy(self.get().dir)
+
+    dictSpecialAttrs = { "base" : __get_base_path,
+                         "posix" : __get_posix_path,
+                         "srcpath" : __get_srcnode,
+                         "srcdir" : __get_srcdir,
+                         "dir" : __get_dir }
+
+    def __getattr__(self, name):
+        # This is how we implement the "special" attributes
+        # such as base, posix, srcdir, etc.
+        try:
+            return self.dictSpecialAttrs[name](self)
+        except KeyError:
+            return SCons.Util.Proxy.__getattr__(self, name)
+        
+
 class Entry(SCons.Node.Node):
     """A generic class for file system entries.  This class is for
     when we don't know yet whether the entry being looked up is a file
@@ -251,14 +306,14 @@ class Entry(SCons.Node.Node):
 
         assert directory, "A directory must be provided"
 
-        self.abspath_str = directory.abspath_ + name
+        self.abspath = directory.abspath_ + name
         if directory.path == '.':
             self.path = name
         else:
             self.path = directory.path_ + name
 
         self.path_ = self.path
-        self.abspath_ = self.abspath_str
+        self.abspath_ = self.abspath
         self.dir = directory
         self.cwd = None # will hold the SConscript directory for target nodes
         self.duplicate = directory.duplicate
@@ -293,11 +348,11 @@ class Entry(SCons.Node.Node):
         Since this should return the real contents from the file
         system, we check to see into what sort of subclass we should
         morph this Entry."""
-        if os.path.isfile(self.abspath_str):
+        if os.path.isfile(self.abspath):
             self.__class__ = File
             self._morph()
             return File.get_contents(self)
-        if os.path.isdir(self.abspath_str):
+        if os.path.isdir(self.abspath):
             self.__class__ = Dir
             self._morph()
             return Dir.get_contents(self)
@@ -307,7 +362,7 @@ class Entry(SCons.Node.Node):
         try:
             return self._exists
         except AttributeError:
-            self._exists = _existsp(self.abspath_str)
+            self._exists = _existsp(self.abspath)
             return self._exists
 
     def rexists(self):
@@ -403,64 +458,9 @@ class Entry(SCons.Node.Node):
             self.sbuilder = scb
         return scb
 
-    def get_base_path(self):
-        """Return the file's directory and file name, with the
-        suffix stripped."""
-        return os.path.splitext(self.get_path())[0]
-
-    def get_suffix(self):
-        """Return the file's suffix."""
-        return os.path.splitext(self.get_path())[1]
-
-    def get_file_name(self):
-        """Return the file's name without the path."""
-        return self.name
-
-    def get_file_base(self):
-        """Return the file name with path and suffix stripped."""
-        return os.path.splitext(self.name)[0]
-
-    def get_posix_path(self):
-        """Return the path with / as the path separator, regardless
-        of platform."""
-        if os.sep == '/':
-            return str(self)
-        else:
-            return string.replace(self.get_path(), os.sep, '/')
-
     def get_abspath(self):
         """Get the absolute path of the file."""
-        return self.abspath_str
-
-    def get_srcdir(self):
-        """Returns the directory containing the source node linked to this
-        node via BuildDir(), or the directory of this node if not linked."""
-        return self.srcnode().dir
-
-    dictSpecialAttrs = { "file" : get_file_name,
-                         "base" : get_base_path,
-                         "filebase" : get_file_base,
-                         "suffix" : get_suffix,
-                         "posix" : get_posix_path,
-                         "abspath" : get_abspath,
-                         "srcpath" : srcnode,
-                         "srcdir" : get_srcdir }
-
-    def __getattr__(self, name):
-        # This is how we implement the "special" attributes
-        # such as base, suffix, basepath, etc.
-        #
-        # Note that we enclose values in a SCons.Util.Literal instance,
-        # so they will retain special characters during Environment variable
-        # substitution.
-        try:
-            attr = self.dictSpecialAttrs[name](self)
-        except KeyError:
-            raise AttributeError, '%s has no attribute: %s' % (self.__class__, name)
-        if SCons.Util.is_String(attr):
-            return SCons.Util.SpecialAttrWrapper(attr, self.name +
-                                                 "_%s" % name)
-        return attr
+        return self.abspath
 
     def for_signature(self):
         # Return just our name.  Even an absolute path would not work,
@@ -468,6 +468,14 @@ class Entry(SCons.Node.Node):
         # paths.
         return self.name
 
+    def get_subst_proxy(self):
+        try:
+            return self._proxy
+        except AttributeError:
+            ret = EntryProxy(self)
+            self._proxy = ret
+            return ret
+
 # This is for later so we can differentiate between Entry the class and Entry
 # the method of the FS class.
 _classEntry = Entry
@@ -556,7 +564,7 @@ class FS:
                     raise SCons.Errors.UserError
                 dir = Dir(drive, ParentOfRoot(), self)
                 dir.path = dir.path + os.sep
-                dir.abspath_str = dir.abspath_str + os.sep
+                dir.abspath = dir.abspath + os.sep
                 self.Root[drive] = dir
                 directory = dir
             path_comp = path_comp[1:]
@@ -642,7 +650,7 @@ class FS:
             if not dir is None:
                 self._cwd = dir
                 if change_os_dir:
-                    os.chdir(dir.abspath_str)
+                    os.chdir(dir.abspath)
         except:
             self._cwd = curr
             raise
@@ -850,7 +858,7 @@ class Dir(Entry):
         node) don't use signatures for currency calculation."""
 
         self.path_ = self.path + os.sep
-        self.abspath_ = self.abspath_str + os.sep
+        self.abspath_ = self.abspath + os.sep
         self.repositories = []
         self.srcdir = None
         
@@ -951,9 +959,9 @@ class Dir(Entry):
         keys = filter(lambda k: k != '.' and k != '..', self.entries.keys())
         kids = map(lambda x, s=self: s.entries[x], keys)
         def c(one, two):
-            if one.abspath_str < two.abspath_str:
+            if one.abspath < two.abspath:
                return -1
-            if one.abspath_str > two.abspath_str:
+            if one.abspath > two.abspath:
                return 1
             return 0
         kids.sort(c)
@@ -1085,11 +1093,11 @@ class File(Entry):
     def get_contents(self):
         if not self.rexists():
             return ''
-        return open(self.rfile().abspath_str, "rb").read()
+        return open(self.rfile().abspath, "rb").read()
 
     def get_timestamp(self):
         if self.rexists():
-            return os.path.getmtime(self.rfile().abspath_str)
+            return os.path.getmtime(self.rfile().abspath)
         else:
             return 0
 
@@ -1328,7 +1336,7 @@ class File(Entry):
         # Duplicate from source path if we are set up to do this.
         if self.duplicate and not self.has_builder() and not self.linked:
             src=self.srcnode().rfile()
-            if src.exists() and src.abspath_str != self.abspath_str:
+            if src.exists() and src.abspath != self.abspath:
                 self._createDir()
                 try:
                     Unlink(self, None, None)
index 209e80d61c0a915df15c149bb76a0d3a766447ed..1f582641c4aa5ecad3253bdbc9d1e796acdbe763 100644 (file)
@@ -1524,7 +1524,7 @@ class SpecialAttrTestCase(unittest.TestCase):
         test=TestCmd(workdir='')
         fs = SCons.Node.FS.FS(test.workpath(''))
 
-        f=fs.Entry('foo/bar/baz.blat')
+        f=fs.Entry('foo/bar/baz.blat').get_subst_proxy()
         assert str(f.dir) == os.path.normpath('foo/bar'), str(f.dir)
         assert f.dir.is_literal()
         assert f.dir.for_signature() == 'bar', f.dir.for_signature()
@@ -1564,11 +1564,11 @@ class SpecialAttrTestCase(unittest.TestCase):
 
         assert str(f.srcpath) == os.path.normpath('baz/bar/baz.blat'), str(f.srcpath)
         assert f.srcpath.is_literal()
-        assert isinstance(f.srcpath, SCons.Node.FS.Entry)
+        assert isinstance(f.srcpath.get(), SCons.Node.FS.Entry)
         
         assert str(f.srcdir) == os.path.normpath('baz/bar'), str(f.srcdir)
         assert f.srcdir.is_literal()
-        assert isinstance(f.srcdir, SCons.Node.FS.Dir)
+        assert isinstance(f.srcdir.get(), SCons.Node.FS.Dir)
 
         # And now, combinations!!!
         assert str(f.srcpath.base) == os.path.normpath('baz/bar/baz'), str(f.srcpath.base)
index b188f815ce033f7a7d179be137949049316c304b..2cf6d4b7caea8d90af8f0910c49a4cd21dbce4a1 100644 (file)
@@ -949,6 +949,12 @@ class NodeTestCase(unittest.TestCase):
         assert n.found_includes == {}, n.found_includes
         assert n.implicit is None, n.implicit
 
+    def test_get_subst_proxy(self):
+        """Test the get_subst_proxy method."""
+        n = MyNode("test")
+
+        assert n.get_subst_proxy() == n, n.get_subst_proxy()
+
 
 if __name__ == "__main__":
     suite = unittest.makeSuite(NodeTestCase, 'test_')
index 883d75739da953ca0e69c4508eb9544e79018709..5231f902aed9d1abaa38b7167427ce3e1c62d8d1 100644 (file)
@@ -628,7 +628,7 @@ class Node:
         generator is being called to generate a signature for the
         command line, which determines if we should rebuild or not.
 
-        Such command generators should use this method in preference
+        Such command generators shoud use this method in preference
         to str(Node) when converting a Node to a string, passing
         in the for_signature parameter, such that we will call
         Node.for_signature() or str(Node) properly, depending on whether
@@ -638,6 +638,21 @@ class Node:
             return self.for_signature()
         return str(self)
 
+    def get_subst_proxy(self):
+        """
+        This method is expected to return an object that will function
+        exactly like this Node, except that it implements any additional
+        special features that we would like to be in effect for
+        Environment variable substitution.  The principle use is that
+        some Nodes would like to implement a __getattr__() method,
+        but putting that in the Node type itself has a tendency to kill
+        performance.  We instead put it in a proxy and return it from
+        this method.  It is legal for this method to return self
+        if no new functionality is needed for Environment substitution.
+        """
+        return self
+        
+
 def get_children(node, parent): return node.children()
 def ignore_cycle(node, stack): pass
 def do_nothing(node, parent): pass
index b7aac31cd4f4c34eda05592cb78a0abf7ff750ee..8d81437f5eb43c1e2df4227103c3f48e52c4f9d3 100644 (file)
@@ -341,17 +341,26 @@ class DisplayEngine:
         else:
             self.__call__ = self.dont_print
 
+def target_prep(target):
+    if target and not isinstance(target, NodeList):
+        if not is_List(target):
+            target = [target]
+        target = NodeList(map(lambda x: x.get_subst_proxy(), target))
+    return target
+
+def source_prep(source):
+    if source and not isinstance(source, NodeList):
+        if not is_List(source):
+            source = [source]
+        source = NodeList(map(lambda x: x.rfile().get_subst_proxy(), source))
+    return source
 
 def subst_dict(target, source, env):
-    """Create a dictionary for substitution of construction
-    variables.
+    """Create a dictionary for substitution of special
+    construction variables.
 
     This translates the following special arguments:
 
-    env    - the construction environment itself,
-             the values of which (CC, CCFLAGS, etc.)
-             are copied straight into the dictionary
-
     target - the target (object or array of objects),
              used to generate the TARGET and TARGETS
              construction variables
@@ -359,20 +368,20 @@ def subst_dict(target, source, env):
     source - the source (object or array of objects),
              used to generate the SOURCES and SOURCE
              construction variables
-    """
-
-    dict = env.Dictionary().copy()
 
-    if not is_List(target):
-        target = [target]
+    env    - the construction Environment used for this
+             build, which is made available as the __env__
+             construction variable
+    """
+    dict = { '__env__' : env }
 
-    dict['TARGETS'] = NodeList(target)
+    target = target_prep(target)
+    dict['TARGETS'] = target
     if dict['TARGETS']:
         dict['TARGET'] = dict['TARGETS'][0]
 
-    if not is_List(source):
-        source = [source]
-    dict['SOURCES'] = NodeList(map(lambda x: x.rfile(), source))
+    source = source_prep(source)
+    dict['SOURCES'] = source
     if dict['SOURCES']:
         dict['SOURCE'] = dict['SOURCES'][0]
 
@@ -406,8 +415,7 @@ def _canonicalize(obj):
 _regex_remove = [ None, _rm, _remove ]
 _strconv = [ to_String, to_String, _canonicalize ]
 
-def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None,
-                     source=None):
+def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None):
     """
     This function serves the same purpose as scons_subst(), except
     this function returns the interpolated list as a list of lines, where
@@ -434,18 +442,13 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None,
 
     remove = _regex_remove[mode]
     strconv = _strconv[mode]
-    
-    if target != None:
-        dict = subst_dict(target, source, env)
-    else:
-        dict = env.Dictionary()
 
     def repl(m,
              target=target,
              source=source,
              env=env,
-             local_vars = dict,
-             global_vars = { "__env__" : env },
+             local_vars = subst_dict(target, source, env),
+             global_vars = env.Dictionary(),
              strconv=strconv,
              sig=(mode != SUBST_CMD)):
         key = m.group(1)
@@ -492,8 +495,7 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None,
     return map(lambda x: map(CmdStringHolder, filter(lambda y:y, string.split(x, '\0\1'))),
                listLines)
 
-def scons_subst(strSubst, env, mode=SUBST_RAW, target=None,
-                source=None):
+def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None):
     """Recursively interpolates dictionary variables into
     the specified string, returning the expanded result.
     Variables are specified by a $ prefix in the string and
@@ -506,11 +508,6 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None,
 
     # This function needs to be fast, so don't call scons_subst_list
 
-    if target != None:
-        dict = subst_dict(target, source, env)
-    else:
-        dict = env.Dictionary()
-
     remove = _regex_remove[mode]
     strconv = _strconv[mode]
 
@@ -518,8 +515,8 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None,
              target=target,
              source=source,
              env=env,
-             local_vars = dict,
-             global_vars = { '__env__' : env },
+             local_vars = subst_dict(target, source, env),
+             global_vars = env.Dictionary(),
              strconv=strconv,
              sig=(mode != SUBST_CMD)):
         key = m.group(1)
@@ -530,7 +527,8 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None,
         except NameError:
             return '\0\5'
         if callable(e):
-            e = e(target=target, source=source, env=env, for_signature=sig)
+            e = e(target=target, source=source, env=env,
+                  for_signature = sig)
 
         def conv(arg, strconv=strconv):
             literal = 0
@@ -691,6 +689,9 @@ class Proxy:
     def __getattr__(self, name):
         return getattr(self.__subject, name)
 
+    def get(self):
+        return self.__subject
+
 # attempt to load the windows registry module:
 can_read_reg = 0
 try:
index e0543d9a894a74af174975d323d85f57fb5ca6c5..a48b3021d5542c369fa4b9c0f9fc52010bcdcd39 100644 (file)
@@ -95,6 +95,9 @@ class UtilTestCase(unittest.TestCase):
             def rfile(self):
                 return self
 
+            def get_subst_proxy(self):
+                return self
+
             foo = 1
         
         target = [ N("./foo/bar.exe"),
@@ -241,6 +244,8 @@ class UtilTestCase(unittest.TestCase):
                 return 1
             def rfile(self):
                 return self
+            def get_subst_proxy(self):
+                return self
         
         loc = {}
         target = [ Node("./foo/bar.exe"),
@@ -589,6 +594,7 @@ class UtilTestCase(unittest.TestCase):
         s.baz = 6
 
         assert p.baz == 5, p.baz
+        assert p.get() == s, p.get()
 
     def test_Literal(self):
         """Test the Literal() function."""
@@ -710,9 +716,9 @@ class UtilTestCase(unittest.TestCase):
     def test_subst_dict(self):
         """Test substituting dictionary values in an Action
         """
-        d = subst_dict([], [], DummyEnv({'a' : 'A', 'b' : 'B'}))
-        assert d['a'] == 'A', d
-        assert d['b'] == 'B', d
+        env = DummyEnv({'a' : 'A', 'b' : 'B'})
+        d = subst_dict([], [], env)
+        assert d['__env__'] is env, d['__env__']
 
         class SimpleNode:
             def __init__(self, data):
@@ -723,6 +729,8 @@ class UtilTestCase(unittest.TestCase):
                 return self
             def is_literal(self):
                 return 1
+            def get_subst_proxy(self):
+                return self
             
         d = subst_dict(target = SimpleNode('t'), source = SimpleNode('s'), env=DummyEnv())
         assert str(d['TARGETS'][0]) == 't', d['TARGETS']
@@ -749,6 +757,8 @@ class UtilTestCase(unittest.TestCase):
                 return self.name
             def rfile(self):
                 return self.__class__('rstr-' + self.name)
+            def get_subst_proxy(self):
+                return self
 
         d = subst_dict(target = [N('t3'), SimpleNode('t4')],
                        source = [SimpleNode('s3'), N('s4')],