http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / Node / FSTests.py
1 #
2 # __COPYRIGHT__
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 #
23
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
25
26 import SCons.compat
27
28 import os
29 import os.path
30 import sys
31 import time
32 import unittest
33 from TestCmd import TestCmd
34 import shutil
35 import stat
36
37 import SCons.Errors
38 import SCons.Node.FS
39 import SCons.Util
40 import SCons.Warnings
41
42 built_it = None
43
44 scanner_count = 0
45
46 class Scanner:
47     def __init__(self, node=None):
48         global scanner_count
49         scanner_count = scanner_count + 1
50         self.hash = scanner_count
51         self.node = node
52     def path(self, env, dir, target=None, source=None):
53         return ()
54     def __call__(self, node, env, path):
55         return [self.node]
56     def __hash__(self):
57         return self.hash
58     def select(self, node):
59         return self
60     def recurse_nodes(self, nodes):
61         return nodes
62
63 class Environment:
64     def __init__(self):
65         self.scanner = Scanner()
66     def Dictionary(self, *args):
67         return {}
68     def autogenerate(self, **kw):
69         return {}
70     def get_scanner(self, skey):
71         return self.scanner
72     def Override(self, overrides):
73         return self
74     def _update(self, dict):
75         pass
76
77 class Action:
78     def __call__(self, targets, sources, env, **kw):
79         global built_it
80         if kw.get('execute', 1):
81             built_it = 1
82         return 0
83     def show(self, string):
84         pass
85     def get_contents(self, target, source, env):
86         return ""
87     def genstring(self, target, source, env):
88         return ""
89     def strfunction(self, targets, sources, env):
90         return ""
91     def get_implicit_deps(self, target, source, env):
92         return []
93
94 class Builder:
95     def __init__(self, factory, action=Action()):
96         self.factory = factory
97         self.env = Environment()
98         self.overrides = {}
99         self.action = action
100         self.target_scanner = None
101         self.source_scanner = None
102
103     def targets(self, t):
104         return [t]
105
106     def source_factory(self, name):
107         return self.factory(name)
108
109 class _tempdirTestCase(unittest.TestCase):
110     def setUp(self):
111         self.save_cwd = os.getcwd()
112         self.test = TestCmd(workdir='')
113         # FS doesn't like the cwd to be something other than its root.
114         os.chdir(self.test.workpath(""))
115         self.fs = SCons.Node.FS.FS()
116
117     def tearDown(self):
118         os.chdir(self.save_cwd)
119
120 class VariantDirTestCase(unittest.TestCase):
121     def runTest(self):
122         """Test variant dir functionality"""
123         test=TestCmd(workdir='')
124
125         fs = SCons.Node.FS.FS()
126         f1 = fs.File('build/test1')
127         fs.VariantDir('build', 'src')
128         f2 = fs.File('build/test2')
129         d1 = fs.Dir('build')
130         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
131         assert f2.srcnode().path == os.path.normpath('src/test2'), f2.srcnode().path
132         assert d1.srcnode().path == 'src', d1.srcnode().path
133
134         fs = SCons.Node.FS.FS()
135         f1 = fs.File('build/test1')
136         fs.VariantDir('build', '.')
137         f2 = fs.File('build/test2')
138         d1 = fs.Dir('build')
139         assert f1.srcnode().path == 'test1', f1.srcnode().path
140         assert f2.srcnode().path == 'test2', f2.srcnode().path
141         assert d1.srcnode().path == '.', d1.srcnode().path
142
143         fs = SCons.Node.FS.FS()
144         fs.VariantDir('build/var1', 'src')
145         fs.VariantDir('build/var2', 'src')
146         f1 = fs.File('build/var1/test1')
147         f2 = fs.File('build/var2/test1')
148         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
149         assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path
150
151         fs = SCons.Node.FS.FS()
152         fs.VariantDir('../var1', 'src')
153         fs.VariantDir('../var2', 'src')
154         f1 = fs.File('../var1/test1')
155         f2 = fs.File('../var2/test1')
156         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
157         assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path
158
159         # Set up some files
160         test.subdir('work', ['work', 'src'])
161         test.subdir(['work', 'build'], ['work', 'build', 'var1'])
162         test.subdir(['work', 'build', 'var2'])
163         test.subdir('rep1', ['rep1', 'src'])
164         test.subdir(['rep1', 'build'], ['rep1', 'build', 'var1'])
165         test.subdir(['rep1', 'build', 'var2'])
166
167         # A source file in the source directory
168         test.write([ 'work', 'src', 'test.in' ], 'test.in')
169
170         # A source file in a subdir of the source directory
171         test.subdir([ 'work', 'src', 'new_dir' ])
172         test.write([ 'work', 'src', 'new_dir', 'test9.out' ], 'test9.out\n')
173
174         # A source file in the repository
175         test.write([ 'rep1', 'src', 'test2.in' ], 'test2.in')
176
177         # Some source files in the variant directory
178         test.write([ 'work', 'build', 'var2', 'test.in' ], 'test.old')
179         test.write([ 'work', 'build', 'var2', 'test2.in' ], 'test2.old')
180
181         # An old derived file in the variant directories
182         test.write([ 'work', 'build', 'var1', 'test.out' ], 'test.old')
183         test.write([ 'work', 'build', 'var2', 'test.out' ], 'test.old')
184
185         # And just in case we are weird, a derived file in the source
186         # dir.
187         test.write([ 'work', 'src', 'test.out' ], 'test.out.src')
188
189         # A derived file in the repository
190         test.write([ 'rep1', 'build', 'var1', 'test2.out' ], 'test2.out_rep')
191         test.write([ 'rep1', 'build', 'var2', 'test2.out' ], 'test2.out_rep')
192
193         os.chdir(test.workpath('work'))
194
195         fs = SCons.Node.FS.FS(test.workpath('work'))
196         fs.VariantDir('build/var1', 'src', duplicate=0)
197         fs.VariantDir('build/var2', 'src')
198         f1 = fs.File('build/var1/test.in')
199         f1out = fs.File('build/var1/test.out')
200         f1out.builder = 1
201         f1out_2 = fs.File('build/var1/test2.out')
202         f1out_2.builder = 1
203         f2 = fs.File('build/var2/test.in')
204         f2out = fs.File('build/var2/test.out')
205         f2out.builder = 1
206         f2out_2 = fs.File('build/var2/test2.out')
207         f2out_2.builder = 1
208         fs.Repository(test.workpath('rep1'))
209
210         assert f1.srcnode().path == os.path.normpath('src/test.in'),\
211                f1.srcnode().path
212         # str(node) returns source path for duplicate = 0
213         assert str(f1) == os.path.normpath('src/test.in'), str(f1)
214         # Build path does not exist
215         assert not f1.exists()
216         # ...but the actual file is not there...
217         assert not os.path.exists(f1.get_abspath())
218         # And duplicate=0 should also work just like a Repository
219         assert f1.rexists()
220         # rfile() should point to the source path
221         assert f1.rfile().path == os.path.normpath('src/test.in'),\
222                f1.rfile().path
223
224         assert f2.srcnode().path == os.path.normpath('src/test.in'),\
225                f2.srcnode().path
226         # str(node) returns build path for duplicate = 1
227         assert str(f2) == os.path.normpath('build/var2/test.in'), str(f2)
228         # Build path exists
229         assert f2.exists()
230         # ...and exists() should copy the file from src to build path
231         assert test.read(['work', 'build', 'var2', 'test.in']) == 'test.in',\
232                test.read(['work', 'build', 'var2', 'test.in'])
233         # Since exists() is true, so should rexists() be
234         assert f2.rexists()
235
236         f3 = fs.File('build/var1/test2.in')
237         f4 = fs.File('build/var2/test2.in')
238
239         assert f3.srcnode().path == os.path.normpath('src/test2.in'),\
240                f3.srcnode().path
241         # str(node) returns source path for duplicate = 0
242         assert str(f3) == os.path.normpath('src/test2.in'), str(f3)
243         # Build path does not exist
244         assert not f3.exists()
245         # Source path does not either
246         assert not f3.srcnode().exists()
247         # But we do have a file in the Repository
248         assert f3.rexists()
249         # rfile() should point to the source path
250         assert f3.rfile().path == os.path.normpath(test.workpath('rep1/src/test2.in')),\
251                f3.rfile().path
252
253         assert f4.srcnode().path == os.path.normpath('src/test2.in'),\
254                f4.srcnode().path
255         # str(node) returns build path for duplicate = 1
256         assert str(f4) == os.path.normpath('build/var2/test2.in'), str(f4)
257         # Build path should exist
258         assert f4.exists()
259         # ...and copy over the file into the local build path
260         assert test.read(['work', 'build', 'var2', 'test2.in']) == 'test2.in'
261         # should exist in repository, since exists() is true
262         assert f4.rexists()
263         # rfile() should point to ourselves
264         assert f4.rfile().path == os.path.normpath('build/var2/test2.in'),\
265                f4.rfile().path
266
267         f5 = fs.File('build/var1/test.out')
268         f6 = fs.File('build/var2/test.out')
269
270         assert f5.exists()
271         # We should not copy the file from the source dir, since this is
272         # a derived file.
273         assert test.read(['work', 'build', 'var1', 'test.out']) == 'test.old'
274
275         assert f6.exists()
276         # We should not copy the file from the source dir, since this is
277         # a derived file.
278         assert test.read(['work', 'build', 'var2', 'test.out']) == 'test.old'
279
280         f7 = fs.File('build/var1/test2.out')
281         f8 = fs.File('build/var2/test2.out')
282
283         assert not f7.exists()
284         assert f7.rexists()
285         r = f7.rfile().path
286         expect = os.path.normpath(test.workpath('rep1/build/var1/test2.out'))
287         assert r == expect, (repr(r), repr(expect))
288
289         assert not f8.exists()
290         assert f8.rexists()
291         assert f8.rfile().path == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\
292                f8.rfile().path
293
294         # Verify the Mkdir and Link actions are called
295         d9 = fs.Dir('build/var2/new_dir')
296         f9 = fs.File('build/var2/new_dir/test9.out')
297
298         class MkdirAction(Action):
299             def __init__(self, dir_made):
300                 self.dir_made = dir_made
301             def __call__(self, target, source, env, executor=None):
302                 if executor:
303                     target = executor.get_all_targets()
304                     source = executor.get_all_sources()
305                 self.dir_made.extend(target)
306
307         save_Link = SCons.Node.FS.Link
308         link_made = []
309         def link_func(target, source, env, link_made=link_made):
310             link_made.append(target)
311         SCons.Node.FS.Link = link_func
312
313         try:
314             dir_made = []
315             d9.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
316             d9.reset_executor()
317             f9.exists()
318             expect = os.path.join('build', 'var2', 'new_dir')
319             assert dir_made[0].path == expect, dir_made[0].path
320             expect = os.path.join('build', 'var2', 'new_dir', 'test9.out')
321             assert link_made[0].path == expect, link_made[0].path
322             assert f9.linked
323         finally:
324             SCons.Node.FS.Link = save_Link
325
326         # Test for an interesting pathological case...we have a source
327         # file in a build path, but not in a source path.  This can
328         # happen if you switch from duplicate=1 to duplicate=0, then
329         # delete a source file.  At one time, this would cause exists()
330         # to return a 1 but get_contents() to throw.
331         test.write([ 'work', 'build', 'var1', 'asourcefile' ], 'stuff')
332         f10 = fs.File('build/var1/asourcefile')
333         assert f10.exists()
334         assert f10.get_contents() == 'stuff', f10.get_contents()
335
336         f11 = fs.File('src/file11')
337         t, m = f11.alter_targets()
338         bdt = [n.path for n in t]
339         var1_file11 = os.path.normpath('build/var1/file11')
340         var2_file11 = os.path.normpath('build/var2/file11')
341         assert bdt == [var1_file11, var2_file11], bdt
342
343         f12 = fs.File('src/file12')
344         f12.builder = 1
345         bdt, m = f12.alter_targets()
346         assert bdt == [], [n.path for n in bdt]
347
348         d13 = fs.Dir('src/new_dir')
349         t, m = d13.alter_targets()
350         bdt = [n.path for n in t]
351         var1_new_dir = os.path.normpath('build/var1/new_dir')
352         var2_new_dir = os.path.normpath('build/var2/new_dir')
353         assert bdt == [var1_new_dir, var2_new_dir], bdt
354
355         # Test that an IOError trying to Link a src file
356         # into a VariantDir ends up throwing a StopError.
357         fIO = fs.File("build/var2/IOError")
358
359         save_Link = SCons.Node.FS.Link
360         def Link_IOError(target, source, env):
361             raise IOError(17, "Link_IOError")
362         SCons.Node.FS.Link = SCons.Action.Action(Link_IOError, None)
363
364         test.write(['work', 'src', 'IOError'], "work/src/IOError\n")
365
366         try:
367             exc_caught = 0
368             try:
369                 fIO.exists()
370             except SCons.Errors.StopError:
371                 exc_caught = 1
372             assert exc_caught, "Should have caught a StopError"
373
374         finally:
375             SCons.Node.FS.Link = save_Link
376
377         # Test to see if Link() works...
378         test.subdir('src','build')
379         test.write('src/foo', 'src/foo\n')
380         os.chmod(test.workpath('src/foo'), stat.S_IRUSR)
381         SCons.Node.FS.Link(fs.File(test.workpath('build/foo')),
382                            fs.File(test.workpath('src/foo')),
383                            None)
384         os.chmod(test.workpath('src/foo'), stat.S_IRUSR | stat.S_IWRITE)
385         st=os.stat(test.workpath('build/foo'))
386         assert (stat.S_IMODE(st[stat.ST_MODE]) & stat.S_IWRITE), \
387                stat.S_IMODE(st[stat.ST_MODE])
388
389         # This used to generate a UserError when we forbid the source
390         # directory from being outside the top-level SConstruct dir.
391         fs = SCons.Node.FS.FS()
392         fs.VariantDir('build', '/test/foo')
393
394         exc_caught = 0
395         try:
396             try:
397                 fs = SCons.Node.FS.FS()
398                 fs.VariantDir('build', 'build/src')
399             except SCons.Errors.UserError:
400                 exc_caught = 1
401             assert exc_caught, "Should have caught a UserError."
402         finally:
403             test.unlink( "src/foo" )
404             test.unlink( "build/foo" )
405
406         fs = SCons.Node.FS.FS()
407         fs.VariantDir('build', 'src1')
408
409         # Calling the same VariantDir twice should work fine.
410         fs.VariantDir('build', 'src1')
411
412         # Trying to move a variant dir to a second source dir
413         # should blow up
414         try:
415             fs.VariantDir('build', 'src2')
416         except SCons.Errors.UserError:
417             pass
418         else:
419             assert 0, "Should have caught a UserError."
420
421         # Test against a former bug.  Make sure we can get a repository
422         # path for the variant directory itself!
423         fs=SCons.Node.FS.FS(test.workpath('work'))
424         test.subdir('work')
425         fs.VariantDir('build/var3', 'src', duplicate=0)
426         d1 = fs.Dir('build/var3')
427         r = d1.rdir()
428         assert r == d1, "%s != %s" % (r, d1)
429
430         # verify the link creation attempts in file_link()
431         class LinkSimulator :
432             """A class to intercept os.[sym]link() calls and track them."""
433
434             def __init__( self, duplicate, link, symlink, copy ) :
435                 self.duplicate = duplicate
436                 self.have = {}
437                 self.have['hard'] = link
438                 self.have['soft'] = symlink
439                 self.have['copy'] = copy
440
441                 self.links_to_be_called = []
442                 for link in self.duplicate.split('-'):
443                     if self.have[link]:
444                         self.links_to_be_called.append(link)
445
446             def link_fail( self , src , dest ) :
447                 next_link = self.links_to_be_called.pop(0)
448                 assert next_link == "hard", \
449                        "Wrong link order: expected %s to be called "\
450                        "instead of hard" % next_link
451                 raise OSError( "Simulating hard link creation error." )
452
453             def symlink_fail( self , src , dest ) :
454                 next_link = self.links_to_be_called.pop(0)
455                 assert next_link == "soft", \
456                        "Wrong link order: expected %s to be called "\
457                        "instead of soft" % next_link
458                 raise OSError( "Simulating symlink creation error." )
459
460             def copy( self , src , dest ) :
461                 next_link = self.links_to_be_called.pop(0)
462                 assert next_link == "copy", \
463                        "Wrong link order: expected %s to be called "\
464                        "instead of copy" % next_link
465                 # copy succeeds, but use the real copy
466                 self.have['copy'](src, dest)
467         # end class LinkSimulator
468
469         try:
470             SCons.Node.FS.set_duplicate("no-link-order")
471             assert 0, "Expected exception when passing an invalid duplicate to set_duplicate"
472         except SCons.Errors.InternalError:
473             pass
474
475         for duplicate in SCons.Node.FS.Valid_Duplicates:
476             # save the real functions for later restoration
477             try:
478                 real_link = os.link
479             except AttributeError:
480                 real_link = None
481             try:
482                 real_symlink = os.symlink
483             except AttributeError:
484                 real_symlink = None
485             real_copy = shutil.copy2
486
487             simulator = LinkSimulator(duplicate, real_link, real_symlink, real_copy)
488
489             # override the real functions with our simulation
490             os.link = simulator.link_fail
491             os.symlink = simulator.symlink_fail
492             shutil.copy2 = simulator.copy
493
494             try:
495
496                 SCons.Node.FS.set_duplicate(duplicate)
497
498                 src_foo = test.workpath('src', 'foo')
499                 build_foo = test.workpath('build', 'foo')
500
501                 test.write(src_foo, 'src/foo\n')
502                 os.chmod(src_foo, stat.S_IRUSR)
503                 try:
504                     SCons.Node.FS.Link(fs.File(build_foo),
505                                        fs.File(src_foo),
506                                        None)
507                 finally:
508                     os.chmod(src_foo, stat.S_IRUSR | stat.S_IWRITE)
509                     test.unlink(src_foo)
510                     test.unlink(build_foo)
511
512             finally:
513                 # restore the real functions
514                 if real_link:
515                     os.link = real_link
516                 else:
517                     delattr(os, 'link')
518                 if real_symlink:
519                     os.symlink = real_symlink
520                 else:
521                     delattr(os, 'symlink')
522                 shutil.copy2 = real_copy
523
524         # Test VariantDir "reflection," where a same-named subdirectory
525         # exists underneath a variant_dir.
526         fs = SCons.Node.FS.FS()
527         fs.VariantDir('work/src/b1/b2', 'work/src')
528
529         dir_list = [
530                 'work/src',
531                 'work/src/b1',
532                 'work/src/b1/b2',
533                 'work/src/b1/b2/b1',
534                 'work/src/b1/b2/b1/b2',
535                 'work/src/b1/b2/b1/b2/b1',
536                 'work/src/b1/b2/b1/b2/b1/b2',
537         ]
538
539         srcnode_map = {
540                 'work/src/b1/b2' : 'work/src',
541                 'work/src/b1/b2/f' : 'work/src/f',
542                 'work/src/b1/b2/b1' : 'work/src/b1/',
543                 'work/src/b1/b2/b1/f' : 'work/src/b1/f',
544                 'work/src/b1/b2/b1/b2' : 'work/src/b1/b2',
545                 'work/src/b1/b2/b1/b2/f' : 'work/src/b1/b2/f',
546                 'work/src/b1/b2/b1/b2/b1' : 'work/src/b1/b2/b1',
547                 'work/src/b1/b2/b1/b2/b1/f' : 'work/src/b1/b2/b1/f',
548                 'work/src/b1/b2/b1/b2/b1/b2' : 'work/src/b1/b2/b1/b2',
549                 'work/src/b1/b2/b1/b2/b1/b2/f' : 'work/src/b1/b2/b1/b2/f',
550         }
551
552         alter_map = {
553                 'work/src' : 'work/src/b1/b2',
554                 'work/src/f' : 'work/src/b1/b2/f',
555                 'work/src/b1' : 'work/src/b1/b2/b1',
556                 'work/src/b1/f' : 'work/src/b1/b2/b1/f',
557         }
558
559         errors = 0
560
561         for dir in dir_list:
562             dnode = fs.Dir(dir)
563             f = dir + '/f'
564             fnode = fs.File(dir + '/f')
565
566             dp = dnode.srcnode().path
567             expect = os.path.normpath(srcnode_map.get(dir, dir))
568             if dp != expect:
569                 print "Dir `%s' srcnode() `%s' != expected `%s'" % (dir, dp, expect)
570                 errors = errors + 1
571
572             fp = fnode.srcnode().path
573             expect = os.path.normpath(srcnode_map.get(f, f))
574             if fp != expect:
575                 print "File `%s' srcnode() `%s' != expected `%s'" % (f, fp, expect)
576                 errors = errors + 1
577
578         for dir in dir_list:
579             dnode = fs.Dir(dir)
580             f = dir + '/f'
581             fnode = fs.File(dir + '/f')
582
583             t, m = dnode.alter_targets()
584             tp = t[0].path
585             expect = os.path.normpath(alter_map.get(dir, dir))
586             if tp != expect:
587                 print "Dir `%s' alter_targets() `%s' != expected `%s'" % (dir, tp, expect)
588                 errors = errors + 1
589
590             t, m = fnode.alter_targets()
591             tp = t[0].path
592             expect = os.path.normpath(alter_map.get(f, f))
593             if tp != expect:
594                 print "File `%s' alter_targets() `%s' != expected `%s'" % (f, tp, expect)
595                 errors = errors + 1
596
597         self.failIf(errors)
598
599 class BaseTestCase(_tempdirTestCase):
600     def test_stat(self):
601         """Test the Base.stat() method"""
602         test = self.test
603         test.write("e1", "e1\n")
604         fs = SCons.Node.FS.FS()
605
606         e1 = fs.Entry('e1')
607         s = e1.stat()
608         assert s is not None, s
609
610         e2 = fs.Entry('e2')
611         s = e2.stat()
612         assert s is None, s
613
614     def test_getmtime(self):
615         """Test the Base.getmtime() method"""
616         test = self.test
617         test.write("file", "file\n")
618         fs = SCons.Node.FS.FS()
619
620         file = fs.Entry('file')
621         assert file.getmtime()
622
623         file = fs.Entry('nonexistent')
624         mtime = file.getmtime()
625         assert mtime is None, mtime
626
627     def test_getsize(self):
628         """Test the Base.getsize() method"""
629         test = self.test
630         test.write("file", "file\n")
631         fs = SCons.Node.FS.FS()
632
633         file = fs.Entry('file')
634         size = file.getsize()
635         assert size == 5, size
636
637         file = fs.Entry('nonexistent')
638         size = file.getsize()
639         assert size is None, size
640
641     def test_isdir(self):
642         """Test the Base.isdir() method"""
643         test = self.test
644         test.subdir('dir')
645         test.write("file", "file\n")
646         fs = SCons.Node.FS.FS()
647
648         dir = fs.Entry('dir')
649         assert dir.isdir()
650
651         file = fs.Entry('file')
652         assert not file.isdir()
653
654         nonexistent = fs.Entry('nonexistent')
655         assert not nonexistent.isdir()
656
657     def test_isfile(self):
658         """Test the Base.isfile() method"""
659         test = self.test
660         test.subdir('dir')
661         test.write("file", "file\n")
662         fs = SCons.Node.FS.FS()
663
664         dir = fs.Entry('dir')
665         assert not dir.isfile()
666
667         file = fs.Entry('file')
668         assert file.isfile()
669
670         nonexistent = fs.Entry('nonexistent')
671         assert not nonexistent.isfile()
672
673     if hasattr(os, 'symlink'):
674         def test_islink(self):
675             """Test the Base.islink() method"""
676             test = self.test
677             test.subdir('dir')
678             test.write("file", "file\n")
679             test.symlink("symlink", "symlink")
680             fs = SCons.Node.FS.FS()
681
682             dir = fs.Entry('dir')
683             assert not dir.islink()
684
685             file = fs.Entry('file')
686             assert not file.islink()
687
688             symlink = fs.Entry('symlink')
689             assert symlink.islink()
690
691             nonexistent = fs.Entry('nonexistent')
692             assert not nonexistent.islink()
693
694 class DirNodeInfoTestCase(_tempdirTestCase):
695     def test___init__(self):
696         """Test DirNodeInfo initialization"""
697         ddd = self.fs.Dir('ddd')
698         ni = SCons.Node.FS.DirNodeInfo(ddd)
699
700 class DirBuildInfoTestCase(_tempdirTestCase):
701     def test___init__(self):
702         """Test DirBuildInfo initialization"""
703         ddd = self.fs.Dir('ddd')
704         bi = SCons.Node.FS.DirBuildInfo(ddd)
705
706 class FileNodeInfoTestCase(_tempdirTestCase):
707     def test___init__(self):
708         """Test FileNodeInfo initialization"""
709         fff = self.fs.File('fff')
710         ni = SCons.Node.FS.FileNodeInfo(fff)
711         assert isinstance(ni, SCons.Node.FS.FileNodeInfo)
712
713     def test_update(self):
714         """Test updating a File.NodeInfo with on-disk information"""
715         test = self.test
716         fff = self.fs.File('fff')
717
718         ni = SCons.Node.FS.FileNodeInfo(fff)
719
720         test.write('fff', "fff\n")
721
722         st = os.stat('fff')
723
724         ni.update(fff)
725
726         assert hasattr(ni, 'timestamp')
727         assert hasattr(ni, 'size')
728
729         ni.timestamp = 0
730         ni.size = 0
731
732         ni.update(fff)
733
734         mtime = st[stat.ST_MTIME]
735         assert ni.timestamp == mtime, (ni.timestamp, mtime)
736         size = st[stat.ST_SIZE]
737         assert ni.size == size, (ni.size, size)
738
739         import time
740         time.sleep(2)
741
742         test.write('fff', "fff longer size, different time stamp\n")
743
744         st = os.stat('fff')
745
746         mtime = st[stat.ST_MTIME]
747         assert ni.timestamp != mtime, (ni.timestamp, mtime)
748         size = st[stat.ST_SIZE]
749         assert ni.size != size, (ni.size, size)
750
751         #fff.clear()
752         #ni.update(fff)
753
754         #st = os.stat('fff')
755
756         #mtime = st[stat.ST_MTIME]
757         #assert ni.timestamp == mtime, (ni.timestamp, mtime)
758         #size = st[stat.ST_SIZE]
759         #assert ni.size == size, (ni.size, size)
760
761 class FileBuildInfoTestCase(_tempdirTestCase):
762     def test___init__(self):
763         """Test File.BuildInfo initialization"""
764         fff = self.fs.File('fff')
765         bi = SCons.Node.FS.FileBuildInfo(fff)
766         assert bi, bi
767
768     def test_convert_to_sconsign(self):
769         """Test converting to .sconsign file format"""
770         fff = self.fs.File('fff')
771         bi = SCons.Node.FS.FileBuildInfo(fff)
772         assert hasattr(bi, 'convert_to_sconsign')
773
774     def test_convert_from_sconsign(self):
775         """Test converting from .sconsign file format"""
776         fff = self.fs.File('fff')
777         bi = SCons.Node.FS.FileBuildInfo(fff)
778         assert hasattr(bi, 'convert_from_sconsign')
779
780     def test_prepare_dependencies(self):
781         """Test that we have a prepare_dependencies() method"""
782         fff = self.fs.File('fff')
783         bi = SCons.Node.FS.FileBuildInfo(fff)
784         bi.prepare_dependencies()
785
786     def test_format(self):
787         """Test the format() method"""
788         f1 = self.fs.File('f1')
789         bi1 = SCons.Node.FS.FileBuildInfo(f1)
790
791         s1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n1'))
792         s1sig.csig = 1
793         d1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n2'))
794         d1sig.timestamp = 2
795         i1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n3'))
796         i1sig.size = 3
797
798         bi1.bsources = [self.fs.File('s1')]
799         bi1.bdepends = [self.fs.File('d1')]
800         bi1.bimplicit = [self.fs.File('i1')]
801         bi1.bsourcesigs = [s1sig]
802         bi1.bdependsigs = [d1sig]
803         bi1.bimplicitsigs = [i1sig]
804         bi1.bact = 'action'
805         bi1.bactsig = 'actionsig'
806
807         expect_lines = [
808             's1: 1 None None',
809             'd1: None 2 None',
810             'i1: None None 3',
811             'actionsig [action]',
812         ]
813
814         expect = '\n'.join(expect_lines)
815         format = bi1.format()
816         assert format == expect, (repr(expect), repr(format))
817
818 class FSTestCase(_tempdirTestCase):
819     def test_runTest(self):
820         """Test FS (file system) Node operations
821
822         This test case handles all of the file system node
823         tests in one environment, so we don't have to set up a
824         complicated directory structure for each test individually.
825         """
826         test = self.test
827
828         test.subdir('sub', ['sub', 'dir'])
829
830         wp = test.workpath('')
831         sub = test.workpath('sub', '')
832         sub_dir = test.workpath('sub', 'dir', '')
833         sub_dir_foo = test.workpath('sub', 'dir', 'foo', '')
834         sub_dir_foo_bar = test.workpath('sub', 'dir', 'foo', 'bar', '')
835         sub_foo = test.workpath('sub', 'foo', '')
836
837         os.chdir(sub_dir)
838
839         fs = SCons.Node.FS.FS()
840
841         e1 = fs.Entry('e1')
842         assert isinstance(e1, SCons.Node.FS.Entry)
843
844         d1 = fs.Dir('d1')
845         assert isinstance(d1, SCons.Node.FS.Dir)
846         assert d1.cwd is d1, d1
847
848         f1 = fs.File('f1', directory = d1)
849         assert isinstance(f1, SCons.Node.FS.File)
850
851         d1_f1 = os.path.join('d1', 'f1')
852         assert f1.path == d1_f1, "f1.path %s != %s" % (f1.path, d1_f1)
853         assert str(f1) == d1_f1, "str(f1) %s != %s" % (str(f1), d1_f1)
854
855         x1 = d1.File('x1')
856         assert isinstance(x1, SCons.Node.FS.File)
857         assert str(x1) == os.path.join('d1', 'x1')
858
859         x2 = d1.Dir('x2')
860         assert isinstance(x2, SCons.Node.FS.Dir)
861         assert str(x2) == os.path.join('d1', 'x2')
862
863         x3 = d1.Entry('x3')
864         assert isinstance(x3, SCons.Node.FS.Entry)
865         assert str(x3) == os.path.join('d1', 'x3')
866
867         assert d1.File(x1) == x1
868         assert d1.Dir(x2) == x2
869         assert d1.Entry(x3) == x3
870
871         x1.cwd = d1
872
873         x4 = x1.File('x4')
874         assert str(x4) == os.path.join('d1', 'x4')
875
876         x5 = x1.Dir('x5')
877         assert str(x5) == os.path.join('d1', 'x5')
878
879         x6 = x1.Entry('x6')
880         assert str(x6) == os.path.join('d1', 'x6')
881         x7 = x1.Entry('x7')
882         assert str(x7) == os.path.join('d1', 'x7')
883
884         assert x1.File(x4) == x4
885         assert x1.Dir(x5) == x5
886         assert x1.Entry(x6) == x6
887         assert x1.Entry(x7) == x7
888
889         assert x1.Entry(x5) == x5
890         try:
891             x1.File(x5)
892         except TypeError:
893             pass
894         else:
895             raise Exception("did not catch expected TypeError")
896
897         assert x1.Entry(x4) == x4
898         try:
899             x1.Dir(x4)
900         except TypeError:
901             pass
902         else:
903             raise Exception("did not catch expected TypeError")
904
905         x6 = x1.File(x6)
906         assert isinstance(x6, SCons.Node.FS.File)
907
908         x7 = x1.Dir(x7)
909         assert isinstance(x7, SCons.Node.FS.Dir)
910
911         seps = [os.sep]
912         if os.sep != '/':
913             seps = seps + ['/']
914
915         drive, path = os.path.splitdrive(os.getcwd())
916
917         def _do_Dir_test(lpath, path_, abspath_, up_path_, sep, fileSys=fs, drive=drive):
918             dir = fileSys.Dir(lpath.replace('/', sep))
919
920             if os.sep != '/':
921                 path_ = path_.replace('/', os.sep)
922                 abspath_ = abspath_.replace('/', os.sep)
923                 up_path_ = up_path_.replace('/', os.sep)
924
925             def strip_slash(p, drive=drive):
926                 if p[-1] == os.sep and len(p) > 1:
927                     p = p[:-1]
928                 if p[0] == os.sep:
929                     p = drive + p
930                 return p
931             path = strip_slash(path_)
932             abspath = strip_slash(abspath_)
933             up_path = strip_slash(up_path_)
934             name = abspath.split(os.sep)[-1]
935
936             assert dir.name == name, \
937                    "dir.name %s != expected name %s" % \
938                    (dir.name, name)
939             assert dir.path == path, \
940                    "dir.path %s != expected path %s" % \
941                    (dir.path, path)
942             assert str(dir) == path, \
943                    "str(dir) %s != expected path %s" % \
944                    (str(dir), path)
945             assert dir.get_abspath() == abspath, \
946                    "dir.abspath %s != expected absolute path %s" % \
947                    (dir.get_abspath(), abspath)
948             assert dir.up().path == up_path, \
949                    "dir.up().path %s != expected parent path %s" % \
950                    (dir.up().path, up_path)
951
952         for sep in seps:
953
954             def Dir_test(lpath, path_, abspath_, up_path_, sep=sep, func=_do_Dir_test):
955                 return func(lpath, path_, abspath_, up_path_, sep)
956
957             Dir_test('',            './',          sub_dir,           sub)
958             Dir_test('foo',         'foo/',        sub_dir_foo,       './')
959             Dir_test('foo/bar',     'foo/bar/',    sub_dir_foo_bar,   'foo/')
960             Dir_test('/foo',        '/foo/',       '/foo/',           '/')
961             Dir_test('/foo/bar',    '/foo/bar/',   '/foo/bar/',       '/foo/')
962             Dir_test('..',          sub,           sub,               wp)
963             Dir_test('foo/..',      './',          sub_dir,           sub)
964             Dir_test('../foo',      sub_foo,       sub_foo,           sub)
965             Dir_test('.',           './',          sub_dir,           sub)
966             Dir_test('./.',         './',          sub_dir,           sub)
967             Dir_test('foo/./bar',   'foo/bar/',    sub_dir_foo_bar,   'foo/')
968             Dir_test('#../foo',     sub_foo,       sub_foo,           sub)
969             Dir_test('#/../foo',    sub_foo,       sub_foo,           sub)
970             Dir_test('#foo/bar',    'foo/bar/',    sub_dir_foo_bar,   'foo/')
971             Dir_test('#/foo/bar',   'foo/bar/',    sub_dir_foo_bar,   'foo/')
972             Dir_test('#',           './',          sub_dir,           sub)
973
974             try:
975                 f2 = fs.File(sep.join(['f1', 'f2']), directory = d1)
976             except TypeError, x:
977                 assert str(x) == ("Tried to lookup File '%s' as a Dir." %
978                                   d1_f1), x
979             except:
980                 raise
981
982             try:
983                 dir = fs.Dir(sep.join(['d1', 'f1']))
984             except TypeError, x:
985                 assert str(x) == ("Tried to lookup File '%s' as a Dir." %
986                                   d1_f1), x
987             except:
988                 raise
989
990             try:
991                 f2 = fs.File('d1')
992             except TypeError, x:
993                 assert str(x) == ("Tried to lookup Dir '%s' as a File." %
994                                   'd1'), x
995             except:
996                 raise
997
998         # Test that just specifying the drive works to identify
999         # its root directory.
1000         p = os.path.abspath(test.workpath('root_file'))
1001         drive, path = os.path.splitdrive(p)
1002         if drive:
1003             # The assert below probably isn't correct for the general
1004             # case, but it works for Windows, which covers a lot
1005             # of ground...
1006             dir = fs.Dir(drive)
1007             assert str(dir) == drive + os.sep, str(dir)
1008
1009             # Make sure that lookups with and without the drive are
1010             # equivalent.
1011             p = os.path.abspath(test.workpath('some/file'))
1012             drive, path = os.path.splitdrive(p)
1013
1014             e1 = fs.Entry(p)
1015             e2 = fs.Entry(path)
1016             assert e1 is e2, (e1, e2)
1017             assert str(e1) is str(e2), (str(e1), str(e2))
1018
1019         # Test for a bug in 0.04 that did not like looking up
1020         # dirs with a trailing slash on Windows.
1021         d=fs.Dir('./')
1022         assert d.path == '.', d.abspath
1023         d=fs.Dir('foo/')
1024         assert d.path == 'foo', d.abspath
1025
1026         # Test for sub-classing of node building.
1027         global built_it
1028
1029         built_it = None
1030         assert not built_it
1031         d1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
1032         d1.builder_set(Builder(fs.File))
1033         d1.reset_executor()
1034         d1.env_set(Environment())
1035         d1.build()
1036         assert built_it
1037
1038         built_it = None
1039         assert not built_it
1040         f1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
1041         f1.builder_set(Builder(fs.File))
1042         f1.reset_executor()
1043         f1.env_set(Environment())
1044         f1.build()
1045         assert built_it
1046
1047         def match(path, expect):
1048             expect = expect.replace('/', os.sep)
1049             assert path == expect, "path %s != expected %s" % (path, expect)
1050
1051         e1 = fs.Entry("d1")
1052         assert e1.__class__.__name__ == 'Dir'
1053         match(e1.path, "d1")
1054         match(e1.dir.path, ".")
1055
1056         e2 = fs.Entry("d1/f1")
1057         assert e2.__class__.__name__ == 'File'
1058         match(e2.path, "d1/f1")
1059         match(e2.dir.path, "d1")
1060
1061         e3 = fs.Entry("e3")
1062         assert e3.__class__.__name__ == 'Entry'
1063         match(e3.path, "e3")
1064         match(e3.dir.path, ".")
1065
1066         e4 = fs.Entry("d1/e4")
1067         assert e4.__class__.__name__ == 'Entry'
1068         match(e4.path, "d1/e4")
1069         match(e4.dir.path, "d1")
1070
1071         e5 = fs.Entry("e3/e5")
1072         assert e3.__class__.__name__ == 'Dir'
1073         match(e3.path, "e3")
1074         match(e3.dir.path, ".")
1075         assert e5.__class__.__name__ == 'Entry'
1076         match(e5.path, "e3/e5")
1077         match(e5.dir.path, "e3")
1078
1079         e6 = fs.Dir("d1/e4")
1080         assert e6 is e4
1081         assert e4.__class__.__name__ == 'Dir'
1082         match(e4.path, "d1/e4")
1083         match(e4.dir.path, "d1")
1084
1085         e7 = fs.File("e3/e5")
1086         assert e7 is e5
1087         assert e5.__class__.__name__ == 'File'
1088         match(e5.path, "e3/e5")
1089         match(e5.dir.path, "e3")
1090
1091         fs.chdir(fs.Dir('subdir'))
1092         f11 = fs.File("f11")
1093         match(f11.path, "subdir/f11")
1094         d12 = fs.Dir("d12")
1095         e13 = fs.Entry("subdir/e13")
1096         match(e13.path, "subdir/subdir/e13")
1097         fs.chdir(fs.Dir('..'))
1098
1099         # Test scanning
1100         f1.builder_set(Builder(fs.File))
1101         f1.env_set(Environment())
1102         xyz = fs.File("xyz")
1103         f1.builder.target_scanner = Scanner(xyz)
1104
1105         f1.scan()
1106         assert f1.implicit[0].path == "xyz"
1107         f1.implicit = []
1108         f1.scan()
1109         assert f1.implicit == []
1110         f1.implicit = None
1111         f1.scan()
1112         assert f1.implicit[0].path == "xyz"
1113
1114         # Test underlying scanning functionality in get_found_includes()
1115         env = Environment()
1116         f12 = fs.File("f12")
1117         t1 = fs.File("t1")
1118
1119         deps = f12.get_found_includes(env, None, t1)
1120         assert deps == [], deps
1121
1122         class MyScanner(Scanner):
1123             call_count = 0
1124             def __call__(self, node, env, path):
1125                 self.call_count = self.call_count + 1
1126                 return Scanner.__call__(self, node, env, path)
1127         s = MyScanner(xyz)
1128
1129         deps = f12.get_found_includes(env, s, t1)
1130         assert deps == [xyz], deps
1131         assert s.call_count == 1, s.call_count
1132
1133         f12.built()
1134
1135         deps = f12.get_found_includes(env, s, t1)
1136         assert deps == [xyz], deps
1137         assert s.call_count == 2, s.call_count
1138
1139         env2 = Environment()
1140
1141         deps = f12.get_found_includes(env2, s, t1)
1142         assert deps == [xyz], deps
1143         assert s.call_count == 3, s.call_count
1144
1145
1146
1147         # Make sure we can scan this file even if the target isn't
1148         # a file that has a scanner (it might be an Alias, e.g.).
1149         class DummyNode:
1150             pass
1151
1152         deps = f12.get_found_includes(env, s, DummyNode())
1153         assert deps == [xyz], deps
1154
1155         # Test building a file whose directory is not there yet...
1156         f1 = fs.File(test.workpath("foo/bar/baz/ack"))
1157         assert not f1.dir.exists()
1158         f1.prepare()
1159         f1.build()
1160         assert f1.dir.exists()
1161
1162         os.chdir('..')
1163
1164         # Test getcwd()
1165         fs = SCons.Node.FS.FS()
1166         assert str(fs.getcwd()) == ".", str(fs.getcwd())
1167         fs.chdir(fs.Dir('subdir'))
1168         # The cwd's path is always "."
1169         assert str(fs.getcwd()) == ".", str(fs.getcwd())
1170         assert fs.getcwd().path == 'subdir', fs.getcwd().path
1171         fs.chdir(fs.Dir('../..'))
1172         assert fs.getcwd().path == test.workdir, fs.getcwd().path
1173
1174         f1 = fs.File(test.workpath("do_i_exist"))
1175         assert not f1.exists()
1176         test.write("do_i_exist","\n")
1177         assert not f1.exists(), "exists() call not cached"
1178         f1.built()
1179         assert f1.exists(), "exists() call caching not reset"
1180         test.unlink("do_i_exist")
1181         assert f1.exists()
1182         f1.built()
1183         assert not f1.exists()
1184
1185         # For some reason, in Windows, the \x1a character terminates
1186         # the reading of files in text mode.  This tests that
1187         # get_contents() returns the binary contents.
1188         test.write("binary_file", "Foo\x1aBar")
1189         f1 = fs.File(test.workpath("binary_file"))
1190         assert f1.get_contents() == "Foo\x1aBar", f1.get_contents()
1191
1192         try:
1193             # TODO(1.5)
1194             eval('test_string = u"Foo\x1aBar"')
1195         except SyntaxError:
1196             pass
1197         else:
1198             # This tests to make sure we can decode UTF-8 text files.
1199             test.write("utf8_file", test_string.encode('utf-8'))
1200             f1 = fs.File(test.workpath("utf8_file"))
1201             assert eval('f1.get_text_contents() == u"Foo\x1aBar"'), \
1202                    f1.get_text_contents()
1203
1204         def nonexistent(method, s):
1205             try:
1206                 x = method(s, create = 0)
1207             except SCons.Errors.UserError:
1208                 pass
1209             else:
1210                 raise Exception("did not catch expected UserError")
1211
1212         nonexistent(fs.Entry, 'nonexistent')
1213         nonexistent(fs.Entry, 'nonexistent/foo')
1214
1215         nonexistent(fs.File, 'nonexistent')
1216         nonexistent(fs.File, 'nonexistent/foo')
1217
1218         nonexistent(fs.Dir, 'nonexistent')
1219         nonexistent(fs.Dir, 'nonexistent/foo')
1220
1221         test.write("preserve_me", "\n")
1222         assert os.path.exists(test.workpath("preserve_me"))
1223         f1 = fs.File(test.workpath("preserve_me"))
1224         f1.prepare()
1225         assert os.path.exists(test.workpath("preserve_me"))
1226
1227         test.write("remove_me", "\n")
1228         assert os.path.exists(test.workpath("remove_me"))
1229         f1 = fs.File(test.workpath("remove_me"))
1230         f1.builder = Builder(fs.File)
1231         f1.env_set(Environment())
1232         f1.prepare()
1233         assert not os.path.exists(test.workpath("remove_me"))
1234
1235         e = fs.Entry('e_local')
1236         assert not hasattr(e, '_local')
1237         e.set_local()
1238         assert e._local == 1
1239         f = fs.File('e_local')
1240         assert f._local == 1
1241         f = fs.File('f_local')
1242         assert f._local == 0
1243
1244         #XXX test_is_up_to_date() for directories
1245
1246         #XXX test_sconsign() for directories
1247
1248         #XXX test_set_signature() for directories
1249
1250         #XXX test_build() for directories
1251
1252         #XXX test_root()
1253
1254         # test Entry.get_contents()
1255         e = fs.Entry('does_not_exist')
1256         c = e.get_contents()
1257         assert c == "", c
1258         assert e.__class__ == SCons.Node.FS.Entry
1259
1260         test.write("file", "file\n")
1261         try:
1262             e = fs.Entry('file')
1263             c = e.get_contents()
1264             assert c == "file\n", c
1265             assert e.__class__ == SCons.Node.FS.File
1266         finally:
1267             test.unlink("file")
1268
1269         # test Entry.get_text_contents()
1270         e = fs.Entry('does_not_exist')
1271         c = e.get_text_contents()
1272         assert c == "", c
1273         assert e.__class__ == SCons.Node.FS.Entry
1274
1275         test.write("file", "file\n")
1276         try:
1277             e = fs.Entry('file')
1278             c = e.get_text_contents()
1279             assert c == "file\n", c
1280             assert e.__class__ == SCons.Node.FS.File
1281         finally:
1282             test.unlink("file")
1283
1284         test.subdir("dir")
1285         e = fs.Entry('dir')
1286         c = e.get_contents()
1287         assert c == "", c
1288         assert e.__class__ == SCons.Node.FS.Dir
1289
1290         c = e.get_text_contents()
1291         try:
1292             eval('assert c == u"", c')
1293         except SyntaxError:
1294             assert c == ""
1295
1296         if hasattr(os, 'symlink'):
1297             os.symlink('nonexistent', test.workpath('dangling_symlink'))
1298             e = fs.Entry('dangling_symlink')
1299             c = e.get_contents()
1300             assert e.__class__ == SCons.Node.FS.Entry, e.__class__
1301             assert c == "", c
1302             c = e.get_text_contents()
1303             try:
1304                 eval('assert c == u"", c')
1305             except SyntaxError:
1306                 assert c == "", c
1307
1308         test.write("tstamp", "tstamp\n")
1309         try:
1310             # Okay, *this* manipulation accomodates Windows FAT file systems
1311             # that only have two-second granularity on their timestamps.
1312             # We round down the current time to the nearest even integer
1313             # value, subtract two to make sure the timestamp is not "now,"
1314             # and then convert it back to a float.
1315             tstamp = float(int(time.time() / 2) * 2) - 2
1316             os.utime(test.workpath("tstamp"), (tstamp - 2.0, tstamp))
1317             f = fs.File("tstamp")
1318             t = f.get_timestamp()
1319             assert t == tstamp, "expected %f, got %f" % (tstamp, t)
1320         finally:
1321             test.unlink("tstamp")
1322
1323         test.subdir('tdir1')
1324         d = fs.Dir('tdir1')
1325         t = d.get_timestamp()
1326         assert t == 0, "expected 0, got %s" % str(t)
1327
1328         test.subdir('tdir2')
1329         f1 = test.workpath('tdir2', 'file1')
1330         f2 = test.workpath('tdir2', 'file2')
1331         test.write(f1, 'file1\n')
1332         test.write(f2, 'file2\n')
1333         current_time = float(int(time.time() / 2) * 2)
1334         t1 = current_time - 4.0
1335         t2 = current_time - 2.0
1336         os.utime(f1, (t1 - 2.0, t1))
1337         os.utime(f2, (t2 - 2.0, t2))
1338         d = fs.Dir('tdir2')
1339         fs.File(f1)
1340         fs.File(f2)
1341         t = d.get_timestamp()
1342         assert t == t2, "expected %f, got %f" % (t2, t)
1343
1344         skey = fs.Entry('eee.x').scanner_key()
1345         assert skey == '.x', skey
1346         skey = fs.Entry('eee.xyz').scanner_key()
1347         assert skey == '.xyz', skey
1348
1349         skey = fs.File('fff.x').scanner_key()
1350         assert skey == '.x', skey
1351         skey = fs.File('fff.xyz').scanner_key()
1352         assert skey == '.xyz', skey
1353
1354         skey = fs.Dir('ddd.x').scanner_key()
1355         assert skey is None, skey
1356
1357         test.write("i_am_not_a_directory", "\n")
1358         try:
1359             exc_caught = 0
1360             try:
1361                 fs.Dir(test.workpath("i_am_not_a_directory"))
1362             except TypeError:
1363                 exc_caught = 1
1364             assert exc_caught, "Should have caught a TypeError"
1365         finally:
1366             test.unlink("i_am_not_a_directory")
1367
1368         exc_caught = 0
1369         try:
1370             fs.File(sub_dir)
1371         except TypeError:
1372             exc_caught = 1
1373         assert exc_caught, "Should have caught a TypeError"
1374
1375         # XXX test_is_up_to_date()
1376
1377         d = fs.Dir('dir')
1378         r = d.remove()
1379         assert r is None, r
1380
1381         f = fs.File('does_not_exist')
1382         r = f.remove()
1383         assert r is None, r
1384
1385         test.write('exists', "exists\n")
1386         f = fs.File('exists')
1387         r = f.remove()
1388         assert r, r
1389         assert not os.path.exists(test.workpath('exists')), "exists was not removed"
1390
1391         symlink = test.workpath('symlink')
1392         try:
1393             os.symlink(test.workpath('does_not_exist'), symlink)
1394             assert os.path.islink(symlink)
1395             f = fs.File('symlink')
1396             r = f.remove()
1397             assert r, r
1398             assert not os.path.islink(symlink), "symlink was not removed"
1399         except AttributeError:
1400             pass
1401
1402         test.write('can_not_remove', "can_not_remove\n")
1403         test.writable(test.workpath('.'), 0)
1404         fp = open(test.workpath('can_not_remove'))
1405
1406         f = fs.File('can_not_remove')
1407         exc_caught = 0
1408         try:
1409             r = f.remove()
1410         except OSError:
1411             exc_caught = 1
1412
1413         fp.close()
1414
1415         assert exc_caught, "Should have caught an OSError, r = " + str(r)
1416
1417         f = fs.Entry('foo/bar/baz')
1418         assert f.for_signature() == 'baz', f.for_signature()
1419         assert f.get_string(0) == os.path.normpath('foo/bar/baz'), \
1420                f.get_string(0)
1421         assert f.get_string(1) == 'baz', f.get_string(1)
1422
1423     def test_drive_letters(self):
1424         """Test drive-letter look-ups"""
1425
1426         test = self.test
1427
1428         test.subdir('sub', ['sub', 'dir'])
1429
1430         def drive_workpath(drive, dirs, test=test):
1431             x = test.workpath(*dirs)
1432             drive, path = os.path.splitdrive(x)
1433             return 'X:' + path
1434
1435         wp              = drive_workpath('X:', [''])
1436
1437         if wp[-1] in (os.sep, '/'):
1438             tmp         = os.path.split(wp[:-1])[0]
1439         else:
1440             tmp         = os.path.split(wp)[0]
1441
1442         parent_tmp      = os.path.split(tmp)[0]
1443         if parent_tmp == 'X:':
1444             parent_tmp = 'X:' + os.sep
1445
1446         tmp_foo         = os.path.join(tmp, 'foo')
1447
1448         foo             = drive_workpath('X:', ['foo'])
1449         foo_bar         = drive_workpath('X:', ['foo', 'bar'])
1450         sub             = drive_workpath('X:', ['sub', ''])
1451         sub_dir         = drive_workpath('X:', ['sub', 'dir', ''])
1452         sub_dir_foo     = drive_workpath('X:', ['sub', 'dir', 'foo', ''])
1453         sub_dir_foo_bar = drive_workpath('X:', ['sub', 'dir', 'foo', 'bar', ''])
1454         sub_foo         = drive_workpath('X:', ['sub', 'foo', ''])
1455
1456         fs = SCons.Node.FS.FS()
1457
1458         seps = [os.sep]
1459         if os.sep != '/':
1460             seps = seps + ['/']
1461
1462         def _do_Dir_test(lpath, path_, up_path_, sep, fileSys=fs):
1463             dir = fileSys.Dir(lpath.replace('/', sep))
1464
1465             if os.sep != '/':
1466                 path_ = path_.replace('/', os.sep)
1467                 up_path_ = up_path_.replace('/', os.sep)
1468
1469             def strip_slash(p):
1470                 if p[-1] == os.sep and len(p) > 3:
1471                     p = p[:-1]
1472                 return p
1473             path = strip_slash(path_)
1474             up_path = strip_slash(up_path_)
1475             name = path.split(os.sep)[-1]
1476
1477             assert dir.name == name, \
1478                    "dir.name %s != expected name %s" % \
1479                    (dir.name, name)
1480             assert dir.path == path, \
1481                    "dir.path %s != expected path %s" % \
1482                    (dir.path, path)
1483             assert str(dir) == path, \
1484                    "str(dir) %s != expected path %s" % \
1485                    (str(dir), path)
1486             assert dir.up().path == up_path, \
1487                    "dir.up().path %s != expected parent path %s" % \
1488                    (dir.up().path, up_path)
1489
1490         save_os_path = os.path
1491         save_os_sep = os.sep
1492         try:
1493             import ntpath
1494             os.path = ntpath
1495             os.sep = '\\'
1496             SCons.Node.FS.initialize_do_splitdrive()
1497             SCons.Node.FS.initialize_normpath_check()
1498
1499             for sep in seps:
1500
1501                 def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test):
1502                     return func(lpath, path_, up_path_, sep)
1503
1504                 Dir_test('#X:',         wp,             tmp)
1505                 Dir_test('X:foo',       foo,            wp)
1506                 Dir_test('X:foo/bar',   foo_bar,        foo)
1507                 Dir_test('X:/foo',      'X:/foo',       'X:/')
1508                 Dir_test('X:/foo/bar',  'X:/foo/bar/',  'X:/foo/')
1509                 Dir_test('X:..',        tmp,            parent_tmp)
1510                 Dir_test('X:foo/..',    wp,             tmp)
1511                 Dir_test('X:../foo',    tmp_foo,        tmp)
1512                 Dir_test('X:.',         wp,             tmp)
1513                 Dir_test('X:./.',       wp,             tmp)
1514                 Dir_test('X:foo/./bar', foo_bar,        foo)
1515                 Dir_test('#X:../foo',   tmp_foo,        tmp)
1516                 Dir_test('#X:/../foo',  tmp_foo,        tmp)
1517                 Dir_test('#X:foo/bar',  foo_bar,        foo)
1518                 Dir_test('#X:/foo/bar', foo_bar,        foo)
1519                 Dir_test('#X:/',        wp,             tmp)
1520         finally:
1521             os.path = save_os_path
1522             os.sep = save_os_sep
1523             SCons.Node.FS.initialize_do_splitdrive()
1524             SCons.Node.FS.initialize_normpath_check()
1525
1526     def test_target_from_source(self):
1527         """Test the method for generating target nodes from sources"""
1528         fs = self.fs
1529
1530         x = fs.File('x.c')
1531         t = x.target_from_source('pre-', '-suf')
1532         assert str(t) == 'pre-x-suf', str(t)
1533         assert t.__class__ == SCons.Node.FS.Entry
1534
1535         y = fs.File('dir/y')
1536         t = y.target_from_source('pre-', '-suf')
1537         assert str(t) == os.path.join('dir', 'pre-y-suf'), str(t)
1538         assert t.__class__ == SCons.Node.FS.Entry
1539
1540         z = fs.File('zz')
1541         t = z.target_from_source('pre-', '-suf', lambda x: x[:-1])
1542         assert str(t) == 'pre-z-suf', str(t)
1543         assert t.__class__ == SCons.Node.FS.Entry
1544
1545         d = fs.Dir('ddd')
1546         t = d.target_from_source('pre-', '-suf')
1547         assert str(t) == 'pre-ddd-suf', str(t)
1548         assert t.__class__ == SCons.Node.FS.Entry
1549
1550         e = fs.Entry('eee')
1551         t = e.target_from_source('pre-', '-suf')
1552         assert str(t) == 'pre-eee-suf', str(t)
1553         assert t.__class__ == SCons.Node.FS.Entry
1554
1555     def test_same_name(self):
1556         """Test that a local same-named file isn't found for a Dir lookup"""
1557         test = self.test
1558         fs = self.fs
1559
1560         test.subdir('subdir')
1561         test.write(['subdir', 'build'], "subdir/build\n")
1562
1563         subdir = fs.Dir('subdir')
1564         fs.chdir(subdir, change_os_dir=1)
1565         self.fs._lookup('#build/file', subdir, SCons.Node.FS.File)
1566
1567     def test_above_root(self):
1568         """Testing looking up a path above the root directory"""
1569         test = self.test
1570         fs = self.fs
1571
1572         d1 = fs.Dir('d1')
1573         d2 = d1.Dir('d2')
1574         dirs = os.path.normpath(d2.abspath).split(os.sep)
1575         above_path = os.path.join(*['..']*len(dirs) + ['above'])
1576         above = d2.Dir(above_path)
1577
1578     def test_rel_path(self):
1579         """Test the rel_path() method"""
1580         test = self.test
1581         fs = self.fs
1582
1583         d1 = fs.Dir('d1')
1584         d1_f = d1.File('f')
1585         d1_d2 = d1.Dir('d2')
1586         d1_d2_f = d1_d2.File('f')
1587
1588         d3 = fs.Dir('d3')
1589         d3_f = d3.File('f')
1590         d3_d4 = d3.Dir('d4')
1591         d3_d4_f = d3_d4.File('f')
1592
1593         cases = [
1594                 d1,             d1,             '.',
1595                 d1,             d1_f,           'f',
1596                 d1,             d1_d2,          'd2',
1597                 d1,             d1_d2_f,        'd2/f',
1598                 d1,             d3,             '../d3',
1599                 d1,             d3_f,           '../d3/f',
1600                 d1,             d3_d4,          '../d3/d4',
1601                 d1,             d3_d4_f,        '../d3/d4/f',
1602
1603                 d1_f,           d1,             '.',
1604                 d1_f,           d1_f,           'f',
1605                 d1_f,           d1_d2,          'd2',
1606                 d1_f,           d1_d2_f,        'd2/f',
1607                 d1_f,           d3,             '../d3',
1608                 d1_f,           d3_f,           '../d3/f',
1609                 d1_f,           d3_d4,          '../d3/d4',
1610                 d1_f,           d3_d4_f,        '../d3/d4/f',
1611
1612                 d1_d2,          d1,             '..',
1613                 d1_d2,          d1_f,           '../f',
1614                 d1_d2,          d1_d2,          '.',
1615                 d1_d2,          d1_d2_f,        'f',
1616                 d1_d2,          d3,             '../../d3',
1617                 d1_d2,          d3_f,           '../../d3/f',
1618                 d1_d2,          d3_d4,          '../../d3/d4',
1619                 d1_d2,          d3_d4_f,        '../../d3/d4/f',
1620
1621                 d1_d2_f,        d1,             '..',
1622                 d1_d2_f,        d1_f,           '../f',
1623                 d1_d2_f,        d1_d2,          '.',
1624                 d1_d2_f,        d1_d2_f,        'f',
1625                 d1_d2_f,        d3,             '../../d3',
1626                 d1_d2_f,        d3_f,           '../../d3/f',
1627                 d1_d2_f,        d3_d4,          '../../d3/d4',
1628                 d1_d2_f,        d3_d4_f,        '../../d3/d4/f',
1629         ]
1630
1631         if sys.platform in ('win32',):
1632             x_d1        = fs.Dir(r'X:\d1')
1633             x_d1_d2     = x_d1.Dir('d2')
1634             y_d1        = fs.Dir(r'Y:\d1')
1635             y_d1_d2     = y_d1.Dir('d2')
1636             y_d2        = fs.Dir(r'Y:\d2')
1637
1638             win32_cases = [
1639                 x_d1,           x_d1,           '.',
1640                 x_d1,           x_d1_d2,        'd2',
1641                 x_d1,           y_d1,           r'Y:\d1',
1642                 x_d1,           y_d1_d2,        r'Y:\d1\d2',
1643                 x_d1,           y_d2,           r'Y:\d2',
1644             ]
1645
1646             cases.extend(win32_cases)
1647
1648         failed = 0
1649         while cases:
1650             dir, other, expect = cases[:3]
1651             expect = os.path.normpath(expect)
1652             del cases[:3]
1653             result = dir.rel_path(other)
1654             if result != expect:
1655                 if failed == 0: print
1656                 fmt = "    dir_path(%(dir)s, %(other)s) => '%(result)s' did not match '%(expect)s'"
1657                 print fmt % locals()
1658                 failed = failed + 1
1659         assert failed == 0, "%d rel_path() cases failed" % failed
1660
1661     def test_proxy(self):
1662         """Test a Node.FS object wrapped in a proxy instance"""
1663         f1 = self.fs.File('fff')
1664         class Proxy:
1665             # Simplest possibly Proxy class that works for our test,
1666             # this is stripped down from SCons.Util.Proxy.
1667             def __init__(self, subject):
1668                 self.__subject = subject
1669             def __getattr__(self, name):
1670                 return getattr(self.__subject, name)
1671         p = Proxy(f1)
1672         f2 = self.fs.Entry(p)
1673         assert f1 is f2, (f1, f2)
1674
1675
1676
1677 class DirTestCase(_tempdirTestCase):
1678
1679     def test__morph(self):
1680         """Test handling of actions when morphing an Entry into a Dir"""
1681         test = self.test
1682         e = self.fs.Entry('eee')
1683         x = e.get_executor()
1684         x.add_pre_action('pre')
1685         x.add_post_action('post')
1686         e.must_be_same(SCons.Node.FS.Dir)
1687         a = x.get_action_list()
1688         assert a[0] == 'pre', a
1689         assert a[2] == 'post', a
1690
1691     def test_subclass(self):
1692         """Test looking up subclass of Dir nodes"""
1693         class DirSubclass(SCons.Node.FS.Dir):
1694             pass
1695         sd = self.fs._lookup('special_dir', None, DirSubclass, create=1)
1696         sd.must_be_same(SCons.Node.FS.Dir)
1697
1698     def test_get_env_scanner(self):
1699         """Test the Dir.get_env_scanner() method
1700         """
1701         import SCons.Defaults
1702         d = self.fs.Dir('ddd')
1703         s = d.get_env_scanner(Environment())
1704         assert s is SCons.Defaults.DirEntryScanner, s
1705
1706     def test_get_target_scanner(self):
1707         """Test the Dir.get_target_scanner() method
1708         """
1709         import SCons.Defaults
1710         d = self.fs.Dir('ddd')
1711         s = d.get_target_scanner()
1712         assert s is SCons.Defaults.DirEntryScanner, s
1713
1714     def test_scan(self):
1715         """Test scanning a directory for in-memory entries
1716         """
1717         fs = self.fs
1718
1719         dir = fs.Dir('ddd')
1720         fs.File(os.path.join('ddd', 'f1'))
1721         fs.File(os.path.join('ddd', 'f2'))
1722         fs.File(os.path.join('ddd', 'f3'))
1723         fs.Dir(os.path.join('ddd', 'd1'))
1724         fs.Dir(os.path.join('ddd', 'd1', 'f4'))
1725         fs.Dir(os.path.join('ddd', 'd1', 'f5'))
1726         dir.scan()
1727         kids = sorted([x.path for x in dir.children(None)])
1728         assert kids == [os.path.join('ddd', 'd1'),
1729                         os.path.join('ddd', 'f1'),
1730                         os.path.join('ddd', 'f2'),
1731                         os.path.join('ddd', 'f3')], kids
1732
1733     def test_get_contents(self):
1734         """Test getting the contents for a directory.
1735         """
1736         test = self.test
1737
1738         test.subdir('d')
1739         test.write(['d', 'g'], "67890\n")
1740         test.write(['d', 'f'], "12345\n")
1741         test.subdir(['d','sub'])
1742         test.write(['d', 'sub','h'], "abcdef\n")
1743         test.subdir(['d','empty'])
1744
1745         d = self.fs.Dir('d')
1746         g = self.fs.File(os.path.join('d', 'g'))
1747         f = self.fs.File(os.path.join('d', 'f'))
1748         h = self.fs.File(os.path.join('d', 'sub', 'h'))
1749         e = self.fs.Dir(os.path.join('d', 'empty'))
1750         s = self.fs.Dir(os.path.join('d', 'sub'))
1751
1752         #TODO(1.5) files = d.get_contents().split('\n')
1753         files = d.get_contents().split('\n')
1754
1755         assert e.get_contents() == '', e.get_contents()
1756         assert e.get_text_contents() == '', e.get_text_contents()
1757         assert e.get_csig()+" empty" == files[0], files
1758         assert f.get_csig()+" f" == files[1], files
1759         assert g.get_csig()+" g" == files[2], files
1760         assert s.get_csig()+" sub" == files[3], files
1761
1762     def test_implicit_re_scans(self):
1763         """Test that adding entries causes a directory to be re-scanned
1764         """
1765
1766         fs = self.fs
1767
1768         dir = fs.Dir('ddd')
1769
1770         fs.File(os.path.join('ddd', 'f1'))
1771         dir.scan()
1772         kids = sorted([x.path for x in dir.children()])
1773         assert kids == [os.path.join('ddd', 'f1')], kids
1774
1775         fs.File(os.path.join('ddd', 'f2'))
1776         dir.scan()
1777         kids = sorted([x.path for x in dir.children()])
1778         assert kids == [os.path.join('ddd', 'f1'),
1779                         os.path.join('ddd', 'f2')], kids
1780
1781     def test_entry_exists_on_disk(self):
1782         """Test the Dir.entry_exists_on_disk() method
1783         """
1784         test = self.test
1785
1786         does_not_exist = self.fs.Dir('does_not_exist')
1787         assert not does_not_exist.entry_exists_on_disk('foo')
1788
1789         test.subdir('d')
1790         test.write(['d', 'exists'], "d/exists\n")
1791         test.write(['d', 'Case-Insensitive'], "d/Case-Insensitive\n")
1792
1793         d = self.fs.Dir('d')
1794         assert d.entry_exists_on_disk('exists')
1795         assert not d.entry_exists_on_disk('does_not_exist')
1796
1797         if os.path.normcase("TeSt") != os.path.normpath("TeSt") or sys.platform == "cygwin":
1798             assert d.entry_exists_on_disk('case-insensitive')
1799
1800     def test_srcdir_list(self):
1801         """Test the Dir.srcdir_list() method
1802         """
1803         src = self.fs.Dir('src')
1804         bld = self.fs.Dir('bld')
1805         sub1 = bld.Dir('sub')
1806         sub2 = sub1.Dir('sub')
1807         sub3 = sub2.Dir('sub')
1808         self.fs.VariantDir(bld, src, duplicate=0)
1809         self.fs.VariantDir(sub2, src, duplicate=0)
1810
1811         def check(result, expect):
1812             result = list(map(str, result))
1813             expect = list(map(os.path.normpath, expect))
1814             assert result == expect, result
1815
1816         s = src.srcdir_list()
1817         check(s, [])
1818
1819         s = bld.srcdir_list()
1820         check(s, ['src'])
1821
1822         s = sub1.srcdir_list()
1823         check(s, ['src/sub'])
1824
1825         s = sub2.srcdir_list()
1826         check(s, ['src', 'src/sub/sub'])
1827
1828         s = sub3.srcdir_list()
1829         check(s, ['src/sub', 'src/sub/sub/sub'])
1830
1831         self.fs.VariantDir('src/b1/b2', 'src')
1832         b1 = src.Dir('b1')
1833         b1_b2 = b1.Dir('b2')
1834         b1_b2_b1 = b1_b2.Dir('b1')
1835         b1_b2_b1_b2 = b1_b2_b1.Dir('b2')
1836         b1_b2_b1_b2_sub = b1_b2_b1_b2.Dir('sub')
1837
1838         s = b1.srcdir_list()
1839         check(s, [])
1840
1841         s = b1_b2.srcdir_list()
1842         check(s, ['src'])
1843
1844         s = b1_b2_b1.srcdir_list()
1845         check(s, ['src/b1'])
1846
1847         s = b1_b2_b1_b2.srcdir_list()
1848         check(s, ['src/b1/b2'])
1849
1850         s = b1_b2_b1_b2_sub.srcdir_list()
1851         check(s, ['src/b1/b2/sub'])
1852
1853     def test_srcdir_duplicate(self):
1854         """Test the Dir.srcdir_duplicate() method
1855         """
1856         test = self.test
1857
1858         test.subdir('src0')
1859         test.write(['src0', 'exists'], "src0/exists\n")
1860
1861         bld0 = self.fs.Dir('bld0')
1862         src0 = self.fs.Dir('src0')
1863         self.fs.VariantDir(bld0, src0, duplicate=0)
1864
1865         n = bld0.srcdir_duplicate('does_not_exist')
1866         assert n is None, n
1867         assert not os.path.exists(test.workpath('bld0', 'does_not_exist'))
1868
1869         n = bld0.srcdir_duplicate('exists')
1870         assert str(n) == os.path.normpath('src0/exists'), str(n)
1871         assert not os.path.exists(test.workpath('bld0', 'exists'))
1872
1873         test.subdir('src1')
1874         test.write(['src1', 'exists'], "src0/exists\n")
1875
1876         bld1 = self.fs.Dir('bld1')
1877         src1 = self.fs.Dir('src1')
1878         self.fs.VariantDir(bld1, src1, duplicate=1)
1879
1880         n = bld1.srcdir_duplicate('does_not_exist')
1881         assert n is None, n
1882         assert not os.path.exists(test.workpath('bld1', 'does_not_exist'))
1883
1884         n = bld1.srcdir_duplicate('exists')
1885         assert str(n) == os.path.normpath('bld1/exists'), str(n)
1886         assert os.path.exists(test.workpath('bld1', 'exists'))
1887
1888     def test_srcdir_find_file(self):
1889         """Test the Dir.srcdir_find_file() method
1890         """
1891         test = self.test
1892
1893         return_true = lambda: 1
1894
1895         test.subdir('src0')
1896         test.write(['src0', 'on-disk-f1'], "src0/on-disk-f1\n")
1897         test.write(['src0', 'on-disk-f2'], "src0/on-disk-f2\n")
1898         test.write(['src0', 'on-disk-e1'], "src0/on-disk-e1\n")
1899         test.write(['src0', 'on-disk-e2'], "src0/on-disk-e2\n")
1900
1901         bld0 = self.fs.Dir('bld0')
1902         src0 = self.fs.Dir('src0')
1903         self.fs.VariantDir(bld0, src0, duplicate=0)
1904
1905         derived_f = src0.File('derived-f')
1906         derived_f.is_derived = return_true
1907         exists_f = src0.File('exists-f')
1908         exists_f.exists = return_true
1909
1910         derived_e = src0.Entry('derived-e')
1911         derived_e.is_derived = return_true
1912         exists_e = src0.Entry('exists-e')
1913         exists_e.exists = return_true
1914
1915         def check(result, expect):
1916             result = list(map(str, result))
1917             expect = list(map(os.path.normpath, expect))
1918             assert result == expect, result
1919
1920         # First check from the source directory.
1921         n = src0.srcdir_find_file('does_not_exist')
1922         assert n == (None, None), n
1923
1924         n = src0.srcdir_find_file('derived-f')
1925         check(n, ['src0/derived-f', 'src0'])
1926         n = src0.srcdir_find_file('exists-f')
1927         check(n, ['src0/exists-f', 'src0'])
1928         n = src0.srcdir_find_file('on-disk-f1')
1929         check(n, ['src0/on-disk-f1', 'src0'])
1930
1931         n = src0.srcdir_find_file('derived-e')
1932         check(n, ['src0/derived-e', 'src0'])
1933         n = src0.srcdir_find_file('exists-e')
1934         check(n, ['src0/exists-e', 'src0'])
1935         n = src0.srcdir_find_file('on-disk-e1')
1936         check(n, ['src0/on-disk-e1', 'src0'])
1937
1938         # Now check from the variant directory.
1939         n = bld0.srcdir_find_file('does_not_exist')
1940         assert n == (None, None), n
1941
1942         n = bld0.srcdir_find_file('derived-f')
1943         check(n, ['src0/derived-f', 'bld0'])
1944         n = bld0.srcdir_find_file('exists-f')
1945         check(n, ['src0/exists-f', 'bld0'])
1946         n = bld0.srcdir_find_file('on-disk-f2')
1947         check(n, ['src0/on-disk-f2', 'bld0'])
1948
1949         n = bld0.srcdir_find_file('derived-e')
1950         check(n, ['src0/derived-e', 'bld0'])
1951         n = bld0.srcdir_find_file('exists-e')
1952         check(n, ['src0/exists-e', 'bld0'])
1953         n = bld0.srcdir_find_file('on-disk-e2')
1954         check(n, ['src0/on-disk-e2', 'bld0'])
1955
1956         test.subdir('src1')
1957         test.write(['src1', 'on-disk-f1'], "src1/on-disk-f1\n")
1958         test.write(['src1', 'on-disk-f2'], "src1/on-disk-f2\n")
1959         test.write(['src1', 'on-disk-e1'], "src1/on-disk-e1\n")
1960         test.write(['src1', 'on-disk-e2'], "src1/on-disk-e2\n")
1961
1962         bld1 = self.fs.Dir('bld1')
1963         src1 = self.fs.Dir('src1')
1964         self.fs.VariantDir(bld1, src1, duplicate=1)
1965
1966         derived_f = src1.File('derived-f')
1967         derived_f.is_derived = return_true
1968         exists_f = src1.File('exists-f')
1969         exists_f.exists = return_true
1970
1971         derived_e = src1.Entry('derived-e')
1972         derived_e.is_derived = return_true
1973         exists_e = src1.Entry('exists-e')
1974         exists_e.exists = return_true
1975
1976         # First check from the source directory.
1977         n = src1.srcdir_find_file('does_not_exist')
1978         assert n == (None, None), n
1979
1980         n = src1.srcdir_find_file('derived-f')
1981         check(n, ['src1/derived-f', 'src1'])
1982         n = src1.srcdir_find_file('exists-f')
1983         check(n, ['src1/exists-f', 'src1'])
1984         n = src1.srcdir_find_file('on-disk-f1')
1985         check(n, ['src1/on-disk-f1', 'src1'])
1986
1987         n = src1.srcdir_find_file('derived-e')
1988         check(n, ['src1/derived-e', 'src1'])
1989         n = src1.srcdir_find_file('exists-e')
1990         check(n, ['src1/exists-e', 'src1'])
1991         n = src1.srcdir_find_file('on-disk-e1')
1992         check(n, ['src1/on-disk-e1', 'src1'])
1993
1994         # Now check from the variant directory.
1995         n = bld1.srcdir_find_file('does_not_exist')
1996         assert n == (None, None), n
1997
1998         n = bld1.srcdir_find_file('derived-f')
1999         check(n, ['bld1/derived-f', 'src1'])
2000         n = bld1.srcdir_find_file('exists-f')
2001         check(n, ['bld1/exists-f', 'src1'])
2002         n = bld1.srcdir_find_file('on-disk-f2')
2003         check(n, ['bld1/on-disk-f2', 'bld1'])
2004
2005         n = bld1.srcdir_find_file('derived-e')
2006         check(n, ['bld1/derived-e', 'src1'])
2007         n = bld1.srcdir_find_file('exists-e')
2008         check(n, ['bld1/exists-e', 'src1'])
2009         n = bld1.srcdir_find_file('on-disk-e2')
2010         check(n, ['bld1/on-disk-e2', 'bld1'])
2011
2012     def test_dir_on_disk(self):
2013         """Test the Dir.dir_on_disk() method"""
2014         self.test.subdir('sub', ['sub', 'exists'])
2015         self.test.write(['sub', 'file'], "self/file\n")
2016         sub = self.fs.Dir('sub')
2017
2018         r = sub.dir_on_disk('does_not_exist')
2019         assert not r, r
2020
2021         r = sub.dir_on_disk('exists')
2022         assert r, r
2023
2024         r = sub.dir_on_disk('file')
2025         assert not r, r
2026
2027     def test_file_on_disk(self):
2028         """Test the Dir.file_on_disk() method"""
2029         self.test.subdir('sub', ['sub', 'dir'])
2030         self.test.write(['sub', 'exists'], "self/exists\n")
2031         sub = self.fs.Dir('sub')
2032
2033         r = sub.file_on_disk('does_not_exist')
2034         assert not r, r
2035
2036         r = sub.file_on_disk('exists')
2037         assert r, r
2038
2039         r = sub.file_on_disk('dir')
2040         assert not r, r
2041
2042 class EntryTestCase(_tempdirTestCase):
2043     def test_runTest(self):
2044         """Test methods specific to the Entry sub-class.
2045         """
2046         test = TestCmd(workdir='')
2047         # FS doesn't like the cwd to be something other than its root.
2048         os.chdir(test.workpath(""))
2049
2050         fs = SCons.Node.FS.FS()
2051
2052         e1 = fs.Entry('e1')
2053         e1.rfile()
2054         assert e1.__class__ is SCons.Node.FS.File, e1.__class__
2055
2056         test.subdir('e3d')
2057         test.write('e3f', "e3f\n")
2058
2059         e3d = fs.Entry('e3d')
2060         e3d.get_contents()
2061         assert e3d.__class__ is SCons.Node.FS.Dir, e3d.__class__
2062
2063         e3f = fs.Entry('e3f')
2064         e3f.get_contents()
2065         assert e3f.__class__ is SCons.Node.FS.File, e3f.__class__
2066
2067         e3n = fs.Entry('e3n')
2068         e3n.get_contents()
2069         assert e3n.__class__ is SCons.Node.FS.Entry, e3n.__class__
2070
2071         test.subdir('e4d')
2072         test.write('e4f', "e4f\n")
2073
2074         e4d = fs.Entry('e4d')
2075         exists = e4d.exists()
2076         assert e4d.__class__ is SCons.Node.FS.Dir, e4d.__class__
2077         assert exists, "e4d does not exist?"
2078
2079         e4f = fs.Entry('e4f')
2080         exists = e4f.exists()
2081         assert e4f.__class__ is SCons.Node.FS.File, e4f.__class__
2082         assert exists, "e4f does not exist?"
2083
2084         e4n = fs.Entry('e4n')
2085         exists = e4n.exists()
2086         assert e4n.__class__ is SCons.Node.FS.File, e4n.__class__
2087         assert not exists, "e4n exists?"
2088
2089         class MyCalc:
2090             def __init__(self, val):
2091                 self.max_drift = 0
2092                 class M:
2093                     def __init__(self, val):
2094                         self.val = val
2095                     def collect(self, args):
2096                         return reduce(lambda x, y: x+y, args)
2097                     def signature(self, executor):
2098                         return self.val + 222
2099                 self.module = M(val)
2100
2101         test.subdir('e5d')
2102         test.write('e5f', "e5f\n")
2103
2104     def test_Entry_Entry_lookup(self):
2105         """Test looking up an Entry within another Entry"""
2106         self.fs.Entry('#topdir')
2107         self.fs.Entry('#topdir/a/b/c')
2108
2109
2110
2111 class FileTestCase(_tempdirTestCase):
2112
2113     def test_subclass(self):
2114         """Test looking up subclass of File nodes"""
2115         class FileSubclass(SCons.Node.FS.File):
2116             pass
2117         sd = self.fs._lookup('special_file', None, FileSubclass, create=1)
2118         sd.must_be_same(SCons.Node.FS.File)
2119
2120     def test_Dirs(self):
2121         """Test the File.Dirs() method"""
2122         fff = self.fs.File('subdir/fff')
2123         # This simulates that the SConscript file that defined
2124         # fff is in subdir/.
2125         fff.cwd = self.fs.Dir('subdir')
2126         d1 = self.fs.Dir('subdir/d1')
2127         d2 = self.fs.Dir('subdir/d2')
2128         dirs = fff.Dirs(['d1', 'd2'])
2129         assert dirs == [d1, d2], list(map(str, dirs))
2130
2131     def test_exists(self):
2132         """Test the File.exists() method"""
2133         fs = self.fs
2134         test = self.test
2135
2136         src_f1 = fs.File('src/f1')
2137         assert not src_f1.exists(), "%s apparently exists?" % src_f1
2138
2139         test.subdir('src')
2140         test.write(['src', 'f1'], "src/f1\n")
2141
2142         assert not src_f1.exists(), "%s did not cache previous exists() value" % src_f1
2143         src_f1.clear()
2144         assert src_f1.exists(), "%s apparently does not exist?" % src_f1
2145
2146         test.subdir('build')
2147         fs.VariantDir('build', 'src')
2148         build_f1 = fs.File('build/f1')
2149
2150         assert build_f1.exists(), "%s did not realize that %s exists" % (build_f1, src_f1)
2151         assert os.path.exists(build_f1.abspath), "%s did not get duplicated on disk" % build_f1.abspath
2152
2153         test.unlink(['src', 'f1'])
2154         src_f1.clear()  # so the next exists() call will look on disk again
2155
2156         assert build_f1.exists(), "%s did not cache previous exists() value" % build_f1
2157         build_f1.clear()
2158         build_f1.linked = None
2159         assert not build_f1.exists(), "%s did not realize that %s disappeared" % (build_f1, src_f1)
2160         assert not os.path.exists(build_f1.abspath), "%s did not get removed after %s was removed" % (build_f1, src_f1)
2161
2162
2163
2164 class GlobTestCase(_tempdirTestCase):
2165     def setUp(self):
2166         _tempdirTestCase.setUp(self)
2167
2168         fs = SCons.Node.FS.FS()
2169         self.fs = fs
2170
2171         # Make entries on disk that will not have Nodes, so we can verify
2172         # the behavior of looking for things on disk.
2173         self.test.write('disk-bbb', "disk-bbb\n")
2174         self.test.write('disk-aaa', "disk-aaa\n")
2175         self.test.write('disk-ccc', "disk-ccc\n")
2176         self.test.write('#disk-hash', "#disk-hash\n")
2177         self.test.subdir('disk-sub')
2178         self.test.write(['disk-sub', 'disk-ddd'], "disk-sub/disk-ddd\n")
2179         self.test.write(['disk-sub', 'disk-eee'], "disk-sub/disk-eee\n")
2180         self.test.write(['disk-sub', 'disk-fff'], "disk-sub/disk-fff\n")
2181
2182         # Make some entries that have both Nodes and on-disk entries,
2183         # so we can verify what we do with
2184         self.test.write('both-aaa', "both-aaa\n")
2185         self.test.write('both-bbb', "both-bbb\n")
2186         self.test.write('both-ccc', "both-ccc\n")
2187         self.test.write('#both-hash', "#both-hash\n")
2188         self.test.subdir('both-sub1')
2189         self.test.write(['both-sub1', 'both-ddd'], "both-sub1/both-ddd\n")
2190         self.test.write(['both-sub1', 'both-eee'], "both-sub1/both-eee\n")
2191         self.test.write(['both-sub1', 'both-fff'], "both-sub1/both-fff\n")
2192         self.test.subdir('both-sub2')
2193         self.test.write(['both-sub2', 'both-ddd'], "both-sub2/both-ddd\n")
2194         self.test.write(['both-sub2', 'both-eee'], "both-sub2/both-eee\n")
2195         self.test.write(['both-sub2', 'both-fff'], "both-sub2/both-fff\n")
2196
2197         self.both_aaa = fs.File('both-aaa')
2198         self.both_bbb = fs.File('both-bbb')
2199         self.both_ccc = fs.File('both-ccc')
2200         self._both_hash = fs.File('./#both-hash')
2201         self.both_sub1 = fs.Dir('both-sub1')
2202         self.both_sub1_both_ddd = self.both_sub1.File('both-ddd')
2203         self.both_sub1_both_eee = self.both_sub1.File('both-eee')
2204         self.both_sub1_both_fff = self.both_sub1.File('both-fff')
2205         self.both_sub2 = fs.Dir('both-sub2')
2206         self.both_sub2_both_ddd = self.both_sub2.File('both-ddd')
2207         self.both_sub2_both_eee = self.both_sub2.File('both-eee')
2208         self.both_sub2_both_fff = self.both_sub2.File('both-fff')
2209
2210         # Make various Nodes (that don't have on-disk entries) so we
2211         # can verify how we match them.
2212         self.ggg = fs.File('ggg')
2213         self.hhh = fs.File('hhh')
2214         self.iii = fs.File('iii')
2215         self._hash = fs.File('./#hash')
2216         self.subdir1 = fs.Dir('subdir1')
2217         self.subdir1_lll = self.subdir1.File('lll')
2218         self.subdir1_jjj = self.subdir1.File('jjj')
2219         self.subdir1_kkk = self.subdir1.File('kkk')
2220         self.subdir2 = fs.Dir('subdir2')
2221         self.subdir2_lll = self.subdir2.File('lll')
2222         self.subdir2_kkk = self.subdir2.File('kkk')
2223         self.subdir2_jjj = self.subdir2.File('jjj')
2224         self.sub = fs.Dir('sub')
2225         self.sub_dir3 = self.sub.Dir('dir3')
2226         self.sub_dir3_kkk = self.sub_dir3.File('kkk')
2227         self.sub_dir3_jjj = self.sub_dir3.File('jjj')
2228         self.sub_dir3_lll = self.sub_dir3.File('lll')
2229
2230
2231     def do_cases(self, cases, **kwargs):
2232
2233         # First, execute all of the cases with string=True and verify
2234         # that we get the expected strings returned.  We do this first
2235         # so the Glob() calls don't add Nodes to the self.fs file system
2236         # hierarchy.
2237
2238         import copy
2239         strings_kwargs = copy.copy(kwargs)
2240         strings_kwargs['strings'] = True
2241         for input, string_expect, node_expect in cases:
2242             r = sorted(self.fs.Glob(input, **strings_kwargs))
2243             assert r == string_expect, "Glob(%s, strings=True) expected %s, got %s" % (input, string_expect, r)
2244
2245         # Now execute all of the cases without string=True and look for
2246         # the expected Nodes to be returned.  If we don't have a list of
2247         # actual expected Nodes, that means we're expecting a search for
2248         # on-disk-only files to have returned some newly-created nodes.
2249         # Verify those by running the list through str() before comparing
2250         # them with the expected list of strings.
2251         for input, string_expect, node_expect in cases:
2252             r = self.fs.Glob(input, **kwargs)
2253             if node_expect:
2254                 r.sort(lambda a,b: cmp(a.path, b.path))
2255                 result = []
2256                 for n in node_expect:
2257                     if isinstance(n, str):
2258                         n = self.fs.Entry(n)
2259                     result.append(n)
2260                 fmt = lambda n: "%s %s" % (repr(n), repr(str(n)))
2261             else:
2262                 r = sorted(map(str, r))
2263                 result = string_expect
2264                 fmt = lambda n: n
2265             if r != result:
2266                 import pprint
2267                 print "Glob(%s) expected:" % repr(input)
2268                 pprint.pprint(list(map(fmt, result)))
2269                 print "Glob(%s) got:" % repr(input)
2270                 pprint.pprint(list(map(fmt, r)))
2271                 self.fail()
2272
2273     def test_exact_match(self):
2274         """Test globbing for exact Node matches"""
2275         join = os.path.join
2276
2277         cases = (
2278             ('ggg',         ['ggg'],                    [self.ggg]),
2279
2280             ('subdir1',     ['subdir1'],                [self.subdir1]),
2281
2282             ('subdir1/jjj', [join('subdir1', 'jjj')],   [self.subdir1_jjj]),
2283
2284             ('disk-aaa',    ['disk-aaa'],               None),
2285
2286             ('disk-sub',    ['disk-sub'],               None),
2287
2288             ('both-aaa',    ['both-aaa'],               []),
2289         )
2290
2291         self.do_cases(cases)
2292
2293     def test_subdir_matches(self):
2294         """Test globbing for exact Node matches in subdirectories"""
2295         join = os.path.join
2296
2297         cases = (
2298             ('*/jjj',
2299              [join('subdir1', 'jjj'), join('subdir2', 'jjj')],
2300              [self.subdir1_jjj, self.subdir2_jjj]),
2301
2302             ('*/disk-ddd',
2303              [join('disk-sub', 'disk-ddd')],
2304              None),
2305         )
2306
2307         self.do_cases(cases)
2308
2309     def test_asterisk1(self):
2310         """Test globbing for simple asterisk Node matches (1)"""
2311         cases = (
2312             ('h*',
2313              ['hhh'],
2314              [self.hhh]),
2315
2316             ('*',
2317              ['#both-hash', '#hash',
2318               'both-aaa', 'both-bbb', 'both-ccc',
2319               'both-sub1', 'both-sub2',
2320               'ggg', 'hhh', 'iii',
2321               'sub', 'subdir1', 'subdir2'],
2322              [self._both_hash, self._hash,
2323               self.both_aaa, self.both_bbb, self.both_ccc, 'both-hash',
2324               self.both_sub1, self.both_sub2,
2325               self.ggg, 'hash', self.hhh, self.iii,
2326               self.sub, self.subdir1, self.subdir2]),
2327         )
2328
2329         self.do_cases(cases, ondisk=False)
2330
2331     def test_asterisk2(self):
2332         """Test globbing for simple asterisk Node matches (2)"""
2333         cases = (
2334             ('disk-b*',
2335              ['disk-bbb'],
2336              None),
2337
2338             ('*',
2339              ['#both-hash', '#disk-hash', '#hash',
2340               'both-aaa', 'both-bbb', 'both-ccc',
2341               'both-sub1', 'both-sub2',
2342               'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub',
2343               'ggg', 'hhh', 'iii',
2344               'sub', 'subdir1', 'subdir2'],
2345              ['./#both-hash', './#disk-hash', './#hash',
2346               'both-aaa', 'both-bbb', 'both-ccc', 'both-hash',
2347               'both-sub1', 'both-sub2',
2348               'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub',
2349               'ggg', 'hash', 'hhh', 'iii',
2350               'sub', 'subdir1', 'subdir2']),
2351         )
2352
2353         self.do_cases(cases)
2354
2355     def test_question_mark(self):
2356         """Test globbing for simple question-mark Node matches"""
2357         join = os.path.join
2358
2359         cases = (
2360             ('ii?',
2361              ['iii'],
2362              [self.iii]),
2363
2364             ('both-sub?/both-eee',
2365              [join('both-sub1', 'both-eee'), join('both-sub2', 'both-eee')],
2366              [self.both_sub1_both_eee, self.both_sub2_both_eee]),
2367
2368             ('subdir?/jjj',
2369              [join('subdir1', 'jjj'), join('subdir2', 'jjj')],
2370              [self.subdir1_jjj, self.subdir2_jjj]),
2371
2372             ('disk-cc?',
2373              ['disk-ccc'],
2374              None),
2375         )
2376
2377         self.do_cases(cases)
2378
2379     def test_does_not_exist(self):
2380         """Test globbing for things that don't exist"""
2381
2382         cases = (
2383             ('does_not_exist',  [], []),
2384             ('no_subdir/*',     [], []),
2385             ('subdir?/no_file', [], []),
2386         )
2387
2388         self.do_cases(cases)
2389
2390     def test_subdir_asterisk(self):
2391         """Test globbing for asterisk Node matches in subdirectories"""
2392         join = os.path.join
2393
2394         cases = (
2395             ('*/k*',
2396              [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
2397              [self.subdir1_kkk, self.subdir2_kkk]),
2398
2399             ('both-sub?/*',
2400              [join('both-sub1', 'both-ddd'),
2401               join('both-sub1', 'both-eee'),
2402               join('both-sub1', 'both-fff'),
2403               join('both-sub2', 'both-ddd'),
2404               join('both-sub2', 'both-eee'),
2405               join('both-sub2', 'both-fff')],
2406              [self.both_sub1_both_ddd,
2407               self.both_sub1_both_eee,
2408               self.both_sub1_both_fff,
2409               self.both_sub2_both_ddd,
2410               self.both_sub2_both_eee,
2411               self.both_sub2_both_fff],
2412              ),
2413
2414             ('subdir?/*',
2415              [join('subdir1', 'jjj'),
2416               join('subdir1', 'kkk'),
2417               join('subdir1', 'lll'),
2418               join('subdir2', 'jjj'),
2419               join('subdir2', 'kkk'),
2420               join('subdir2', 'lll')],
2421              [self.subdir1_jjj, self.subdir1_kkk, self.subdir1_lll,
2422               self.subdir2_jjj, self.subdir2_kkk, self.subdir2_lll]),
2423
2424             ('sub/*/*',
2425              [join('sub', 'dir3', 'jjj'),
2426               join('sub', 'dir3', 'kkk'),
2427               join('sub', 'dir3', 'lll')],
2428              [self.sub_dir3_jjj, self.sub_dir3_kkk, self.sub_dir3_lll]),
2429
2430             ('*/k*',
2431              [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
2432              None),
2433
2434             ('subdir?/*',
2435              [join('subdir1', 'jjj'),
2436               join('subdir1', 'kkk'),
2437               join('subdir1', 'lll'),
2438               join('subdir2', 'jjj'),
2439               join('subdir2', 'kkk'),
2440               join('subdir2', 'lll')],
2441              None),
2442
2443             ('sub/*/*',
2444              [join('sub', 'dir3', 'jjj'),
2445               join('sub', 'dir3', 'kkk'),
2446               join('sub', 'dir3', 'lll')],
2447              None),
2448         )
2449
2450         self.do_cases(cases)
2451
2452     def test_subdir_question(self):
2453         """Test globbing for question-mark Node matches in subdirectories"""
2454         join = os.path.join
2455
2456         cases = (
2457             ('*/?kk',
2458              [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
2459              [self.subdir1_kkk, self.subdir2_kkk]),
2460
2461             ('subdir?/l?l',
2462              [join('subdir1', 'lll'), join('subdir2', 'lll')],
2463              [self.subdir1_lll, self.subdir2_lll]),
2464
2465             ('*/disk-?ff',
2466              [join('disk-sub', 'disk-fff')],
2467              None),
2468
2469             ('subdir?/l?l',
2470              [join('subdir1', 'lll'), join('subdir2', 'lll')],
2471              None),
2472         )
2473
2474         self.do_cases(cases)
2475
2476     def test_sort(self):
2477         """Test whether globbing sorts"""
2478         join = os.path.join
2479         # At least sometimes this should return out-of-order items
2480         # if Glob doesn't sort.
2481         # It's not a very good test though since it depends on the
2482         # order returned by glob, which might already be sorted.
2483         g = self.fs.Glob('disk-sub/*', strings=True)
2484         expect = [
2485             os.path.join('disk-sub', 'disk-ddd'),
2486             os.path.join('disk-sub', 'disk-eee'),
2487             os.path.join('disk-sub', 'disk-fff'),
2488         ]
2489         assert g == expect, str(g) + " is not sorted, but should be!"
2490
2491         g = self.fs.Glob('disk-*', strings=True)
2492         expect = [ 'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub' ]
2493         assert g == expect, str(g) + " is not sorted, but should be!"
2494
2495
2496 class RepositoryTestCase(_tempdirTestCase):
2497
2498     def setUp(self):
2499         _tempdirTestCase.setUp(self)
2500
2501         self.test.subdir('rep1', 'rep2', 'rep3', 'work')
2502
2503         self.rep1 = self.test.workpath('rep1')
2504         self.rep2 = self.test.workpath('rep2')
2505         self.rep3 = self.test.workpath('rep3')
2506
2507         os.chdir(self.test.workpath('work'))
2508
2509         self.fs = SCons.Node.FS.FS()
2510         self.fs.Repository(self.rep1, self.rep2, self.rep3)
2511
2512     def test_getRepositories(self):
2513         """Test the Dir.getRepositories() method"""
2514         self.fs.Repository('foo')
2515         self.fs.Repository(os.path.join('foo', 'bar'))
2516         self.fs.Repository('bar/foo')
2517         self.fs.Repository('bar')
2518
2519         expect = [
2520             self.rep1,
2521             self.rep2,
2522             self.rep3,
2523             'foo',
2524             os.path.join('foo', 'bar'),
2525             os.path.join('bar', 'foo'),
2526             'bar'
2527         ]
2528
2529         rep = self.fs.Dir('#').getRepositories()
2530         r = [os.path.normpath(str(x)) for x in rep]
2531         assert r == expect, r
2532
2533     def test_get_all_rdirs(self):
2534         """Test the Dir.get_all_rdirs() method"""
2535         self.fs.Repository('foo')
2536         self.fs.Repository(os.path.join('foo', 'bar'))
2537         self.fs.Repository('bar/foo')
2538         self.fs.Repository('bar')
2539
2540         expect = [
2541             '.',
2542             self.rep1,
2543             self.rep2,
2544             self.rep3,
2545             'foo',
2546             os.path.join('foo', 'bar'),
2547             os.path.join('bar', 'foo'),
2548             'bar'
2549         ]
2550
2551         rep = self.fs.Dir('#').get_all_rdirs()
2552         r = [os.path.normpath(str(x)) for x in rep]
2553         assert r == expect, r
2554
2555     def test_rentry(self):
2556         """Test the Base.entry() method"""
2557         return_true = lambda: 1
2558         return_false = lambda: 0
2559
2560         d1 = self.fs.Dir('d1')
2561         d2 = self.fs.Dir('d2')
2562         d3 = self.fs.Dir('d3')
2563
2564         e1 = self.fs.Entry('e1')
2565         e2 = self.fs.Entry('e2')
2566         e3 = self.fs.Entry('e3')
2567
2568         f1 = self.fs.File('f1')
2569         f2 = self.fs.File('f2')
2570         f3 = self.fs.File('f3')
2571
2572         self.test.write([self.rep1, 'd2'], "")
2573         self.test.subdir([self.rep2, 'd3'])
2574         self.test.write([self.rep3, 'd3'], "")
2575
2576         self.test.write([self.rep1, 'e2'], "")
2577         self.test.subdir([self.rep2, 'e3'])
2578         self.test.write([self.rep3, 'e3'], "")
2579
2580         self.test.write([self.rep1, 'f2'], "")
2581         self.test.subdir([self.rep2, 'f3'])
2582         self.test.write([self.rep3, 'f3'], "")
2583
2584         r = d1.rentry()
2585         assert r is d1, r
2586
2587         r = d2.rentry()
2588         assert not r is d2, r
2589         r = str(r)
2590         assert r == os.path.join(self.rep1, 'd2'), r
2591
2592         r = d3.rentry()
2593         assert not r is d3, r
2594         r = str(r)
2595         assert r == os.path.join(self.rep2, 'd3'), r
2596
2597         r = e1.rentry()
2598         assert r is e1, r
2599
2600         r = e2.rentry()
2601         assert not r is e2, r
2602         r = str(r)
2603         assert r == os.path.join(self.rep1, 'e2'), r
2604
2605         r = e3.rentry()
2606         assert not r is e3, r
2607         r = str(r)
2608         assert r == os.path.join(self.rep2, 'e3'), r
2609
2610         r = f1.rentry()
2611         assert r is f1, r
2612
2613         r = f2.rentry()
2614         assert not r is f2, r
2615         r = str(r)
2616         assert r == os.path.join(self.rep1, 'f2'), r
2617
2618         r = f3.rentry()
2619         assert not r is f3, r
2620         r = str(r)
2621         assert r == os.path.join(self.rep2, 'f3'), r
2622
2623     def test_rdir(self):
2624         """Test the Dir.rdir() method"""
2625         return_true = lambda: 1
2626         return_false = lambda: 0
2627
2628         d1 = self.fs.Dir('d1')
2629         d2 = self.fs.Dir('d2')
2630         d3 = self.fs.Dir('d3')
2631
2632         self.test.subdir([self.rep1, 'd2'])
2633         self.test.write([self.rep2, 'd3'], "")
2634         self.test.subdir([self.rep3, 'd3'])
2635
2636         r = d1.rdir()
2637         assert r is d1, r
2638
2639         r = d2.rdir()
2640         assert not r is d2, r
2641         r = str(r)
2642         assert r == os.path.join(self.rep1, 'd2'), r
2643
2644         r = d3.rdir()
2645         assert not r is d3, r
2646         r = str(r)
2647         assert r == os.path.join(self.rep3, 'd3'), r
2648
2649         e1 = self.fs.Dir('e1')
2650         e1.exists = return_false
2651         e2 = self.fs.Dir('e2')
2652         e2.exists = return_false
2653
2654         # Make sure we match entries in repositories,
2655         # regardless of whether they're derived or not.
2656
2657         re1 = self.fs.Entry(os.path.join(self.rep1, 'e1'))
2658         re1.exists = return_true
2659         re1.is_derived = return_true
2660         re2 = self.fs.Entry(os.path.join(self.rep2, 'e2'))
2661         re2.exists = return_true
2662         re2.is_derived = return_false
2663
2664         r = e1.rdir()
2665         assert r is re1, r
2666
2667         r = e2.rdir()
2668         assert r is re2, r
2669
2670     def test_rfile(self):
2671         """Test the File.rfile() method"""
2672         return_true = lambda: 1
2673         return_false = lambda: 0
2674
2675         f1 = self.fs.File('f1')
2676         f2 = self.fs.File('f2')
2677         f3 = self.fs.File('f3')
2678
2679         self.test.write([self.rep1, 'f2'], "")
2680         self.test.subdir([self.rep2, 'f3'])
2681         self.test.write([self.rep3, 'f3'], "")
2682
2683         r = f1.rfile()
2684         assert r is f1, r
2685
2686         r = f2.rfile()
2687         assert not r is f2, r
2688         r = str(r)
2689         assert r == os.path.join(self.rep1, 'f2'), r
2690
2691         r = f3.rfile()
2692         assert not r is f3, r
2693         r = f3.rstr()
2694         assert r == os.path.join(self.rep3, 'f3'), r
2695
2696         e1 = self.fs.File('e1')
2697         e1.exists = return_false
2698         e2 = self.fs.File('e2')
2699         e2.exists = return_false
2700
2701         # Make sure we match entries in repositories,
2702         # regardless of whether they're derived or not.
2703
2704         re1 = self.fs.Entry(os.path.join(self.rep1, 'e1'))
2705         re1.exists = return_true
2706         re1.is_derived = return_true
2707         re2 = self.fs.Entry(os.path.join(self.rep2, 'e2'))
2708         re2.exists = return_true
2709         re2.is_derived = return_false
2710
2711         r = e1.rfile()
2712         assert r is re1, r
2713
2714         r = e2.rfile()
2715         assert r is re2, r
2716
2717     def test_Rfindalldirs(self):
2718         """Test the Rfindalldirs() methods"""
2719         fs = self.fs
2720         test = self.test
2721
2722         d1 = fs.Dir('d1')
2723         d2 = fs.Dir('d2')
2724         rep1_d1 = fs.Dir(test.workpath('rep1', 'd1'))
2725         rep2_d1 = fs.Dir(test.workpath('rep2', 'd1'))
2726         rep3_d1 = fs.Dir(test.workpath('rep3', 'd1'))
2727         sub = fs.Dir('sub')
2728         sub_d1 = sub.Dir('d1')
2729         rep1_sub_d1 = fs.Dir(test.workpath('rep1', 'sub', 'd1'))
2730         rep2_sub_d1 = fs.Dir(test.workpath('rep2', 'sub', 'd1'))
2731         rep3_sub_d1 = fs.Dir(test.workpath('rep3', 'sub', 'd1'))
2732
2733         r = fs.Top.Rfindalldirs((d1,))
2734         assert r == [d1], list(map(str, r))
2735
2736         r = fs.Top.Rfindalldirs((d1, d2))
2737         assert r == [d1, d2], list(map(str, r))
2738
2739         r = fs.Top.Rfindalldirs(('d1',))
2740         assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(map(str, r))
2741
2742         r = fs.Top.Rfindalldirs(('#d1',))
2743         assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(map(str, r))
2744
2745         r = sub.Rfindalldirs(('d1',))
2746         assert r == [sub_d1, rep1_sub_d1, rep2_sub_d1, rep3_sub_d1], list(map(str, r))
2747
2748         r = sub.Rfindalldirs(('#d1',))
2749         assert r == [d1, rep1_d1, rep2_d1, rep3_d1], list(map(str, r))
2750
2751         r = fs.Top.Rfindalldirs(('d1', d2))
2752         assert r == [d1, rep1_d1, rep2_d1, rep3_d1, d2], list(map(str, r))
2753
2754     def test_rexists(self):
2755         """Test the Entry.rexists() method"""
2756         fs = self.fs
2757         test = self.test
2758
2759         test.write([self.rep1, 'f2'], "")
2760         test.write([self.rep2, "i_exist"], "\n")
2761         test.write(["work", "i_exist_too"], "\n")
2762
2763         fs.VariantDir('build', '.')
2764
2765         f = fs.File(test.workpath("work", "i_do_not_exist"))
2766         assert not f.rexists()
2767
2768         f = fs.File(test.workpath("work", "i_exist"))
2769         assert f.rexists()
2770
2771         f = fs.File(test.workpath("work", "i_exist_too"))
2772         assert f.rexists()
2773
2774         f1 = fs.File(os.path.join('build', 'f1'))
2775         assert not f1.rexists()
2776
2777         f2 = fs.File(os.path.join('build', 'f2'))
2778         assert f2.rexists()
2779
2780     def test_FAT_timestamps(self):
2781         """Test repository timestamps on FAT file systems"""
2782         fs = self.fs
2783         test = self.test
2784
2785         test.write(["rep2", "tstamp"], "tstamp\n")
2786         try:
2787             # Okay, *this* manipulation accomodates Windows FAT file systems
2788             # that only have two-second granularity on their timestamps.
2789             # We round down the current time to the nearest even integer
2790             # value, subtract two to make sure the timestamp is not "now,"
2791             # and then convert it back to a float.
2792             tstamp = float(int(time.time() / 2) * 2) - 2
2793             os.utime(test.workpath("rep2", "tstamp"), (tstamp - 2.0, tstamp))
2794             f = fs.File("tstamp")
2795             t = f.get_timestamp()
2796             assert t == tstamp, "expected %f, got %f" % (tstamp, t)
2797         finally:
2798             test.unlink(["rep2", "tstamp"])
2799
2800     def test_get_contents(self):
2801         """Ensure get_contents() returns binary contents from Repositories"""
2802         fs = self.fs
2803         test = self.test
2804
2805         test.write(["rep3", "contents"], "Con\x1aTents\n")
2806         try:
2807             c = fs.File("contents").get_contents()
2808             assert c == "Con\x1aTents\n", "got '%s'" % c
2809         finally:
2810             test.unlink(["rep3", "contents"])
2811
2812     def test_get_text_contents(self):
2813         """Ensure get_text_contents() returns text contents from
2814         Repositories"""
2815         fs = self.fs
2816         test = self.test
2817
2818         # Use a test string that has a file terminator in it to make
2819         # sure we read the entire file, regardless of its contents.
2820         try:
2821             eval('test_string = u"Con\x1aTents\n"')
2822         except SyntaxError:
2823             import collections
2824             class FakeUnicodeString(collections.UserString):
2825                 def encode(self, encoding):
2826                     return str(self)
2827             test_string = FakeUnicodeString("Con\x1aTents\n")
2828
2829
2830         # Test with ASCII.
2831         test.write(["rep3", "contents"], test_string.encode('ascii'))
2832         try:
2833             c = fs.File("contents").get_text_contents()
2834             assert test_string == c, "got %s" % repr(c)
2835         finally:
2836             test.unlink(["rep3", "contents"])
2837
2838         # Test with utf-8
2839         test.write(["rep3", "contents"], test_string.encode('utf-8'))
2840         try:
2841             c = fs.File("contents").get_text_contents()
2842             assert test_string == c, "got %s" % repr(c)
2843         finally:
2844             test.unlink(["rep3", "contents"])
2845
2846         # Test with utf-16
2847         test.write(["rep3", "contents"], test_string.encode('utf-16'))
2848         try:
2849             c = fs.File("contents").get_text_contents()
2850             assert test_string == c, "got %s" % repr(c)
2851         finally:
2852             test.unlink(["rep3", "contents"])
2853
2854     #def test_is_up_to_date(self):
2855
2856
2857
2858 class find_fileTestCase(unittest.TestCase):
2859     def runTest(self):
2860         """Testing find_file function"""
2861         test = TestCmd(workdir = '')
2862         test.write('./foo', 'Some file\n')
2863         test.write('./foo2', 'Another file\n')
2864         test.subdir('same')
2865         test.subdir('bar')
2866         test.write(['bar', 'on_disk'], 'Another file\n')
2867         test.write(['bar', 'same'], 'bar/same\n')
2868
2869         fs = SCons.Node.FS.FS(test.workpath(""))
2870         # FS doesn't like the cwd to be something other than its root.
2871         os.chdir(test.workpath(""))
2872
2873         node_derived = fs.File(test.workpath('bar/baz'))
2874         node_derived.builder_set(1) # Any non-zero value.
2875         node_pseudo = fs.File(test.workpath('pseudo'))
2876         node_pseudo.set_src_builder(1) # Any non-zero value.
2877
2878         paths = tuple(map(fs.Dir, ['.', 'same', './bar']))
2879         nodes = [SCons.Node.FS.find_file('foo', paths)]
2880         nodes.append(SCons.Node.FS.find_file('baz', paths))
2881         nodes.append(SCons.Node.FS.find_file('pseudo', paths))
2882         nodes.append(SCons.Node.FS.find_file('same', paths))
2883
2884         file_names = list(map(str, nodes))
2885         file_names = list(map(os.path.normpath, file_names))
2886         expect = ['./foo', './bar/baz', './pseudo', './bar/same']
2887         expect = list(map(os.path.normpath, expect))
2888         assert file_names == expect, file_names
2889
2890         # Make sure we don't blow up if there's already a File in place
2891         # of a directory that we'd otherwise try to search.  If this
2892         # is broken, we'll see an exception like "Tried to lookup File
2893         # 'bar/baz' as a Dir.
2894         SCons.Node.FS.find_file('baz/no_file_here', paths)
2895
2896         import io
2897         save_sys_stdout = sys.stdout
2898
2899         try:
2900             sio = io.StringIO()
2901             sys.stdout = sio
2902             SCons.Node.FS.find_file('foo2', paths, verbose="xyz")
2903             expect = "  xyz: looking for 'foo2' in '.' ...\n" + \
2904                      "  xyz: ... FOUND 'foo2' in '.'\n"
2905             c = sio.getvalue()
2906             assert c == expect, c
2907
2908             sio = io.StringIO()
2909             sys.stdout = sio
2910             SCons.Node.FS.find_file('baz2', paths, verbose=1)
2911             expect = "  find_file: looking for 'baz2' in '.' ...\n" + \
2912                      "  find_file: looking for 'baz2' in 'same' ...\n" + \
2913                      "  find_file: looking for 'baz2' in 'bar' ...\n"
2914             c = sio.getvalue()
2915             assert c == expect, c
2916
2917             sio = io.StringIO()
2918             sys.stdout = sio
2919             SCons.Node.FS.find_file('on_disk', paths, verbose=1)
2920             expect = "  find_file: looking for 'on_disk' in '.' ...\n" + \
2921                      "  find_file: looking for 'on_disk' in 'same' ...\n" + \
2922                      "  find_file: looking for 'on_disk' in 'bar' ...\n" + \
2923                      "  find_file: ... FOUND 'on_disk' in 'bar'\n"
2924             c = sio.getvalue()
2925             assert c == expect, c
2926         finally:
2927             sys.stdout = save_sys_stdout
2928
2929 class StringDirTestCase(unittest.TestCase):
2930     def runTest(self):
2931         """Test using a string as the second argument of
2932         File() and Dir()"""
2933
2934         test = TestCmd(workdir = '')
2935         test.subdir('sub')
2936         fs = SCons.Node.FS.FS(test.workpath(''))
2937
2938         d = fs.Dir('sub', '.')
2939         assert str(d) == 'sub', str(d)
2940         assert d.exists()
2941         f = fs.File('file', 'sub')
2942         assert str(f) == os.path.join('sub', 'file')
2943         assert not f.exists()
2944
2945 class stored_infoTestCase(unittest.TestCase):
2946     def runTest(self):
2947         """Test how we store build information"""
2948         test = TestCmd(workdir = '')
2949         test.subdir('sub')
2950         fs = SCons.Node.FS.FS(test.workpath(''))
2951
2952         d = fs.Dir('sub')
2953         f = fs.File('file1', d)
2954         bi = f.get_stored_info()
2955         assert hasattr(bi, 'ninfo')
2956
2957         class MySConsign:
2958             class Null:
2959                 def __init__(self):
2960                     self.xyzzy = 7
2961             def get_entry(self, name):
2962                 return self.Null()
2963
2964         f = fs.File('file2', d)
2965         f.dir.sconsign = MySConsign
2966         bi = f.get_stored_info()
2967         assert bi.xyzzy == 7, bi
2968
2969 class has_src_builderTestCase(unittest.TestCase):
2970     def runTest(self):
2971         """Test the has_src_builder() method"""
2972         test = TestCmd(workdir = '')
2973         fs = SCons.Node.FS.FS(test.workpath(''))
2974         os.chdir(test.workpath(''))
2975         test.subdir('sub1')
2976         test.subdir('sub2', ['sub2', 'SCCS'], ['sub2', 'RCS'])
2977
2978         sub1 = fs.Dir('sub1', '.')
2979         f1 = fs.File('f1', sub1)
2980         f2 = fs.File('f2', sub1)
2981         f3 = fs.File('f3', sub1)
2982         sub2 = fs.Dir('sub2', '.')
2983         f4 = fs.File('f4', sub2)
2984         f5 = fs.File('f5', sub2)
2985         f6 = fs.File('f6', sub2)
2986         f7 = fs.File('f7', sub2)
2987         f8 = fs.File('f8', sub2)
2988
2989         h = f1.has_src_builder()
2990         assert not h, h
2991         h = f1.has_builder()
2992         assert not h, h
2993
2994         b1 = Builder(fs.File)
2995         sub1.set_src_builder(b1)
2996
2997         test.write(['sub1', 'f2'], "sub1/f2\n")
2998         h = f1.has_src_builder()        # cached from previous call
2999         assert not h, h
3000         h = f1.has_builder()            # cached from previous call
3001         assert not h, h
3002         h = f2.has_src_builder()
3003         assert not h, h
3004         h = f2.has_builder()
3005         assert not h, h
3006         h = f3.has_src_builder()
3007         assert h, h
3008         h = f3.has_builder()
3009         assert h, h
3010         assert f3.builder is b1, f3.builder
3011
3012         f7.set_src_builder(b1)
3013         f8.builder_set(b1)
3014
3015         test.write(['sub2', 'SCCS', 's.f5'], "sub2/SCCS/s.f5\n")
3016         test.write(['sub2', 'RCS', 'f6,v'], "sub2/RCS/f6,v\n")
3017         h = f4.has_src_builder()
3018         assert not h, h
3019         h = f4.has_builder()
3020         assert not h, h
3021         h = f5.has_src_builder()
3022         assert h, h
3023         h = f5.has_builder()
3024         assert h, h
3025         h = f6.has_src_builder()
3026         assert h, h
3027         h = f6.has_builder()
3028         assert h, h
3029         h = f7.has_src_builder()
3030         assert h, h
3031         h = f7.has_builder()
3032         assert h, h
3033         h = f8.has_src_builder()
3034         assert not h, h
3035         h = f8.has_builder()
3036         assert h, h
3037
3038 class prepareTestCase(unittest.TestCase):
3039     def runTest(self):
3040         """Test the prepare() method"""
3041
3042         class MyFile(SCons.Node.FS.File):
3043             def _createDir(self, update=None):
3044                 raise SCons.Errors.StopError
3045             def exists(self):
3046                 return None
3047
3048         fs = SCons.Node.FS.FS()
3049         file = MyFile('foo', fs.Dir('.'), fs)
3050
3051         exc_caught = 0
3052         try:
3053             file.prepare()
3054         except SCons.Errors.StopError:
3055             exc_caught = 1
3056         assert exc_caught, "Should have caught a StopError."
3057
3058         class MkdirAction(Action):
3059             def __init__(self, dir_made):
3060                 self.dir_made = dir_made
3061             def __call__(self, target, source, env, executor=None):
3062                 if executor:
3063                     target = executor.get_all_targets()
3064                     source = executor.get_all_sources()
3065                 self.dir_made.extend(target)
3066
3067         dir_made = []
3068         new_dir = fs.Dir("new_dir")
3069         new_dir.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
3070         new_dir.reset_executor()
3071         xyz = fs.File(os.path.join("new_dir", "xyz"))
3072
3073         xyz.set_state(SCons.Node.up_to_date)
3074         xyz.prepare()
3075         assert dir_made == [], dir_made
3076
3077         xyz.set_state(0)
3078         xyz.prepare()
3079         assert dir_made[0].path == "new_dir", dir_made[0]
3080
3081         dir = fs.Dir("dir")
3082         dir.prepare()
3083
3084
3085
3086 class SConstruct_dirTestCase(unittest.TestCase):
3087     def runTest(self):
3088         """Test setting the SConstruct directory"""
3089
3090         fs = SCons.Node.FS.FS()
3091         fs.set_SConstruct_dir(fs.Dir('xxx'))
3092         assert fs.SConstruct_dir.path == 'xxx'
3093
3094
3095
3096 class CacheDirTestCase(unittest.TestCase):
3097
3098     def test_get_cachedir_csig(self):
3099         fs = SCons.Node.FS.FS()
3100
3101         f9 = fs.File('f9')
3102         r = f9.get_cachedir_csig()
3103         assert r == 'd41d8cd98f00b204e9800998ecf8427e', r
3104
3105
3106
3107 class clearTestCase(unittest.TestCase):
3108     def runTest(self):
3109         """Test clearing FS nodes of cached data."""
3110         fs = SCons.Node.FS.FS()
3111         test = TestCmd(workdir='')
3112
3113         e = fs.Entry('e')
3114         assert not e.exists()
3115         assert not e.rexists()
3116         assert str(e) == 'e', str(d)
3117         e.clear()
3118         assert not e.exists()
3119         assert not e.rexists()
3120         assert str(e) == 'e', str(d)
3121
3122         d = fs.Dir(test.workpath('d'))
3123         test.subdir('d')
3124         assert d.exists()
3125         assert d.rexists()
3126         assert str(d) == test.workpath('d'), str(d)
3127         fs.rename(test.workpath('d'), test.workpath('gone'))
3128         # Verify caching is active
3129         assert d.exists(), 'caching not active'
3130         assert d.rexists()
3131         assert str(d) == test.workpath('d'), str(d)
3132         # Now verify clear() resets the cache
3133         d.clear()
3134         assert not d.exists()      
3135         assert not d.rexists()
3136         assert str(d) == test.workpath('d'), str(d)
3137         
3138         f = fs.File(test.workpath('f'))
3139         test.write(test.workpath('f'), 'file f')
3140         assert f.exists()
3141         assert f.rexists()
3142         assert str(f) == test.workpath('f'), str(f)
3143         # Verify caching is active
3144         test.unlink(test.workpath('f'))
3145         assert f.exists()
3146         assert f.rexists()
3147         assert str(f) == test.workpath('f'), str(f)
3148         # Now verify clear() resets the cache
3149         f.clear()
3150         assert not f.exists()
3151         assert not f.rexists()
3152         assert str(f) == test.workpath('f'), str(f)
3153
3154
3155
3156 class disambiguateTestCase(unittest.TestCase):
3157     def runTest(self):
3158         """Test calling the disambiguate() method."""
3159         test = TestCmd(workdir='')
3160
3161         fs = SCons.Node.FS.FS()
3162
3163         ddd = fs.Dir('ddd')
3164         d = ddd.disambiguate()
3165         assert d is ddd, d
3166
3167         fff = fs.File('fff')
3168         f = fff.disambiguate()
3169         assert f is fff, f
3170
3171         test.subdir('edir')
3172         test.write('efile', "efile\n")
3173
3174         edir = fs.Entry(test.workpath('edir'))
3175         d = edir.disambiguate()
3176         assert d.__class__ is ddd.__class__, d.__class__
3177
3178         efile = fs.Entry(test.workpath('efile'))
3179         f = efile.disambiguate()
3180         assert f.__class__ is fff.__class__, f.__class__
3181
3182         test.subdir('build')
3183         test.subdir(['build', 'bdir'])
3184         test.write(['build', 'bfile'], "build/bfile\n")
3185
3186         test.subdir('src')
3187         test.write(['src', 'bdir'], "src/bdir\n")
3188         test.subdir(['src', 'bfile'])
3189
3190         test.subdir(['src', 'edir'])
3191         test.write(['src', 'efile'], "src/efile\n")
3192
3193         fs.VariantDir(test.workpath('build'), test.workpath('src'))
3194
3195         build_bdir = fs.Entry(test.workpath('build/bdir'))
3196         d = build_bdir.disambiguate()
3197         assert d is build_bdir, d
3198         assert d.__class__ is ddd.__class__, d.__class__
3199
3200         build_bfile = fs.Entry(test.workpath('build/bfile'))
3201         f = build_bfile.disambiguate()
3202         assert f is build_bfile, f
3203         assert f.__class__ is fff.__class__, f.__class__
3204
3205         build_edir = fs.Entry(test.workpath('build/edir'))
3206         d = build_edir.disambiguate()
3207         assert d.__class__ is ddd.__class__, d.__class__
3208
3209         build_efile = fs.Entry(test.workpath('build/efile'))
3210         f = build_efile.disambiguate()
3211         assert f.__class__ is fff.__class__, f.__class__
3212
3213         build_nonexistant = fs.Entry(test.workpath('build/nonexistant'))
3214         f = build_nonexistant.disambiguate()
3215         assert f.__class__ is fff.__class__, f.__class__
3216
3217 class postprocessTestCase(unittest.TestCase):
3218     def runTest(self):
3219         """Test calling the postprocess() method."""
3220         fs = SCons.Node.FS.FS()
3221
3222         e = fs.Entry('e')
3223         e.postprocess()
3224
3225         d = fs.Dir('d')
3226         d.postprocess()
3227
3228         f = fs.File('f')
3229         f.postprocess()
3230
3231
3232
3233 class SpecialAttrTestCase(unittest.TestCase):
3234     def runTest(self):
3235         """Test special attributes of file nodes."""
3236         test=TestCmd(workdir='')
3237         fs = SCons.Node.FS.FS(test.workpath('work'))
3238
3239         f = fs.Entry('foo/bar/baz.blat').get_subst_proxy()
3240
3241         s = str(f.dir)
3242         assert s == os.path.normpath('foo/bar'), s
3243         assert f.dir.is_literal(), f.dir
3244         for_sig = f.dir.for_signature()
3245         assert for_sig == 'bar', for_sig
3246
3247         s = str(f.file)
3248         assert s == 'baz.blat', s
3249         assert f.file.is_literal(), f.file
3250         for_sig = f.file.for_signature()
3251         assert for_sig == 'baz.blat_file', for_sig
3252
3253         s = str(f.base)
3254         assert s == os.path.normpath('foo/bar/baz'), s
3255         assert f.base.is_literal(), f.base
3256         for_sig = f.base.for_signature()
3257         assert for_sig == 'baz.blat_base', for_sig
3258
3259         s = str(f.filebase)
3260         assert s == 'baz', s
3261         assert f.filebase.is_literal(), f.filebase
3262         for_sig = f.filebase.for_signature()
3263         assert for_sig == 'baz.blat_filebase', for_sig
3264
3265         s = str(f.suffix)
3266         assert s == '.blat', s
3267         assert f.suffix.is_literal(), f.suffix
3268         for_sig = f.suffix.for_signature()
3269         assert for_sig == 'baz.blat_suffix', for_sig
3270
3271         s = str(f.abspath)
3272         assert s == test.workpath('work', 'foo', 'bar', 'baz.blat'), s
3273         assert f.abspath.is_literal(), f.abspath
3274         for_sig = f.abspath.for_signature()
3275         assert for_sig == 'baz.blat_abspath', for_sig
3276
3277         s = str(f.posix)
3278         assert s == 'foo/bar/baz.blat', s
3279         assert f.posix.is_literal(), f.posix
3280         if f.posix != f:
3281             for_sig = f.posix.for_signature()
3282             assert for_sig == 'baz.blat_posix', for_sig
3283
3284         s = str(f.windows)
3285         assert s == 'foo\\bar\\baz.blat', repr(s)
3286         assert f.windows.is_literal(), f.windows
3287         if f.windows != f:
3288             for_sig = f.windows.for_signature()
3289             assert for_sig == 'baz.blat_windows', for_sig
3290
3291         # Deprecated synonym for the .windows suffix.
3292         s = str(f.win32)
3293         assert s == 'foo\\bar\\baz.blat', repr(s)
3294         assert f.win32.is_literal(), f.win32
3295         if f.win32 != f:
3296             for_sig = f.win32.for_signature()
3297             assert for_sig == 'baz.blat_windows', for_sig
3298
3299         # And now, combinations!!!
3300         s = str(f.srcpath.base)
3301         assert s == os.path.normpath('foo/bar/baz'), s
3302         s = str(f.srcpath.dir)
3303         assert s == str(f.srcdir), s
3304         s = str(f.srcpath.posix)
3305         assert s == 'foo/bar/baz.blat', s
3306         s = str(f.srcpath.windows)
3307         assert s == 'foo\\bar\\baz.blat', s
3308         s = str(f.srcpath.win32)
3309         assert s == 'foo\\bar\\baz.blat', s
3310
3311         # Test what happens with VariantDir()
3312         fs.VariantDir('foo', 'baz')
3313
3314         s = str(f.srcpath)
3315         assert s == os.path.normpath('baz/bar/baz.blat'), s
3316         assert f.srcpath.is_literal(), f.srcpath
3317         g = f.srcpath.get()
3318         assert isinstance(g, SCons.Node.FS.File), g.__class__
3319
3320         s = str(f.srcdir)
3321         assert s == os.path.normpath('baz/bar'), s
3322         assert f.srcdir.is_literal(), f.srcdir
3323         g = f.srcdir.get()
3324         assert isinstance(g, SCons.Node.FS.Dir), g.__class__
3325
3326         # And now what happens with VariantDir() + Repository()
3327         fs.Repository(test.workpath('repository'))
3328
3329         f = fs.Entry('foo/sub/file.suffix').get_subst_proxy()
3330         test.subdir('repository',
3331                     ['repository', 'baz'],
3332                     ['repository', 'baz', 'sub'])
3333
3334         rd = test.workpath('repository', 'baz', 'sub')
3335         rf = test.workpath('repository', 'baz', 'sub', 'file.suffix')
3336         test.write(rf, "\n")
3337
3338         s = str(f.srcpath)
3339         assert s == os.path.normpath('baz/sub/file.suffix'), s
3340         assert f.srcpath.is_literal(), f.srcpath
3341         g = f.srcpath.get()
3342         # Gets disambiguated to SCons.Node.FS.File by get_subst_proxy().
3343         assert isinstance(g, SCons.Node.FS.File), g.__class__
3344
3345         s = str(f.srcdir)
3346         assert s == os.path.normpath('baz/sub'), s
3347         assert f.srcdir.is_literal(), f.srcdir
3348         g = f.srcdir.get()
3349         assert isinstance(g, SCons.Node.FS.Dir), g.__class__
3350
3351         s = str(f.rsrcpath)
3352         assert s == rf, s
3353         assert f.rsrcpath.is_literal(), f.rsrcpath
3354         g = f.rsrcpath.get()
3355         assert isinstance(g, SCons.Node.FS.File), g.__class__
3356
3357         s = str(f.rsrcdir)
3358         assert s == rd, s
3359         assert f.rsrcdir.is_literal(), f.rsrcdir
3360         g = f.rsrcdir.get()
3361         assert isinstance(g, SCons.Node.FS.Dir), g.__class__
3362
3363         # Check that attempts to access non-existent attributes of the
3364         # subst proxy generate the right exceptions and messages.
3365         caught = None
3366         try:
3367             fs.Dir('ddd').get_subst_proxy().no_such_attr
3368         except AttributeError, e:
3369             assert str(e) == "Dir instance 'ddd' has no attribute 'no_such_attr'", e
3370             caught = 1
3371         assert caught, "did not catch expected AttributeError"
3372
3373         caught = None
3374         try:
3375             fs.Entry('eee').get_subst_proxy().no_such_attr
3376         except AttributeError, e:
3377             # Gets disambiguated to File instance by get_subst_proxy().
3378             assert str(e) == "File instance 'eee' has no attribute 'no_such_attr'", e
3379             caught = 1
3380         assert caught, "did not catch expected AttributeError"
3381
3382         caught = None
3383         try:
3384             fs.File('fff').get_subst_proxy().no_such_attr
3385         except AttributeError, e:
3386             assert str(e) == "File instance 'fff' has no attribute 'no_such_attr'", e
3387             caught = 1
3388         assert caught, "did not catch expected AttributeError"
3389
3390
3391
3392 class SaveStringsTestCase(unittest.TestCase):
3393     def runTest(self):
3394         """Test caching string values of nodes."""
3395         test=TestCmd(workdir='')
3396
3397         def setup(fs):
3398             fs.Dir('src')
3399             fs.Dir('d0')
3400             fs.Dir('d1')
3401
3402             d0_f = fs.File('d0/f')
3403             d1_f = fs.File('d1/f')
3404             d0_b = fs.File('d0/b')
3405             d1_b = fs.File('d1/b')
3406             d1_f.duplicate = 1
3407             d1_b.duplicate = 1
3408             d0_b.builder = 1
3409             d1_b.builder = 1
3410
3411             return [d0_f, d1_f, d0_b, d1_b]
3412
3413         def modify(nodes):
3414             d0_f, d1_f, d0_b, d1_b = nodes
3415             d1_f.duplicate = 0
3416             d1_b.duplicate = 0
3417             d0_b.builder = 0
3418             d1_b.builder = 0
3419
3420         fs1 = SCons.Node.FS.FS(test.workpath('fs1'))
3421         nodes = setup(fs1)
3422         fs1.VariantDir('d0', 'src', duplicate=0)
3423         fs1.VariantDir('d1', 'src', duplicate=1)
3424
3425         s = list(map(str, nodes))
3426         expect = list(map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']))
3427         assert s == expect, s
3428
3429         modify(nodes)
3430
3431         s = list(map(str, nodes))
3432         expect = list(map(os.path.normpath, ['src/f', 'src/f', 'd0/b', 'd1/b']))
3433         assert s == expect, s
3434
3435         SCons.Node.FS.save_strings(1)
3436         fs2 = SCons.Node.FS.FS(test.workpath('fs2'))
3437         nodes = setup(fs2)
3438         fs2.VariantDir('d0', 'src', duplicate=0)
3439         fs2.VariantDir('d1', 'src', duplicate=1)
3440
3441         s = list(map(str, nodes))
3442         expect = list(map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']))
3443         assert s == expect, s
3444
3445         modify(nodes)
3446
3447         s = list(map(str, nodes))
3448         expect = list(map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']))
3449         assert s == expect, 'node str() not cached: %s'%s
3450
3451
3452 class AbsolutePathTestCase(unittest.TestCase):
3453     def test_root_lookup_equivalence(self):
3454         """Test looking up /fff vs. fff in the / directory"""
3455         test=TestCmd(workdir='')
3456
3457         fs = SCons.Node.FS.FS('/')
3458
3459         save_cwd = os.getcwd()
3460         try:
3461             os.chdir('/')
3462             fff1 = fs.File('fff')
3463             fff2 = fs.File('/fff')
3464             assert fff1 is fff2, "fff and /fff returned different Nodes!"
3465         finally:
3466             os.chdir(save_cwd)
3467
3468
3469
3470 if __name__ == "__main__":
3471     suite = unittest.TestSuite()
3472     suite.addTest(VariantDirTestCase())
3473     suite.addTest(find_fileTestCase())
3474     suite.addTest(StringDirTestCase())
3475     suite.addTest(stored_infoTestCase())
3476     suite.addTest(has_src_builderTestCase())
3477     suite.addTest(prepareTestCase())
3478     suite.addTest(SConstruct_dirTestCase())
3479     suite.addTest(clearTestCase())
3480     suite.addTest(disambiguateTestCase())
3481     suite.addTest(postprocessTestCase())
3482     suite.addTest(SpecialAttrTestCase())
3483     suite.addTest(SaveStringsTestCase())
3484     tclasses = [
3485         AbsolutePathTestCase,
3486         BaseTestCase,
3487         CacheDirTestCase,
3488         DirTestCase,
3489         DirBuildInfoTestCase,
3490         DirNodeInfoTestCase,
3491         EntryTestCase,
3492         FileTestCase,
3493         FileBuildInfoTestCase,
3494         FileNodeInfoTestCase,
3495         FSTestCase,
3496         GlobTestCase,
3497         RepositoryTestCase,
3498     ]
3499     for tclass in tclasses:
3500         names = unittest.getTestCaseNames(tclass, 'test_')
3501         suite.addTests(list(map(tclass, names)))
3502     if not unittest.TextTestRunner().run(suite).wasSuccessful():
3503         sys.exit(1)
3504
3505 # Local Variables:
3506 # tab-width:4
3507 # indent-tabs-mode:nil
3508 # End:
3509 # vim: set expandtab tabstop=4 shiftwidth=4: