Fix --no-exec handling of cache. (Kevin Quick)
[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 os
27 import os.path
28 import string
29 import sys
30 import time
31 import unittest
32 from TestCmd import TestCmd
33 import shutil
34 import stat
35
36 import SCons.Errors
37 import SCons.Node.FS
38 import SCons.Warnings
39
40 built_it = None
41
42 # This will be built-in in 2.3.  For now fake it.
43 try :
44     True , False
45 except NameError :
46     True = 1 ; False = 0
47
48
49 scanner_count = 0
50
51 class Scanner:
52     def __init__(self, node=None):
53         global scanner_count
54         scanner_count = scanner_count + 1
55         self.hash = scanner_count
56         self.node = node
57     def path(self, env, target):
58         return ()
59     def __call__(self, node, env, path):
60         return [self.node]
61     def __hash__(self):
62         return self.hash
63     def select(self, node):
64         return self
65
66 class Environment:
67     def __init__(self):
68         self.scanner = Scanner()
69     def Dictionary(self, *args):
70         return {}
71     def autogenerate(self, **kw):
72         return {}
73     def get_scanner(self, skey):
74         return self.scanner
75     def Override(self, overrides):
76         return self
77     def _update(self, dict):
78         pass
79
80 class Action:
81     def __call__(self, targets, sources, env, errfunc, **kw):
82         global built_it
83         if kw.get('execute', 1):
84             built_it = 1
85         return 0
86     def show(self, string):
87         pass
88     def strfunction(self, targets, sources, env):
89         return ""
90     def get_actions(self):
91         return [self]
92
93 class Builder:
94     def __init__(self, factory, action=Action()):
95         self.factory = factory
96         self.env = Environment()
97         self.overrides = {}
98         self.action = action
99
100     def get_actions(self):
101         return [self]
102
103     def targets(self, t):
104         return [t]
105
106     def source_factory(self, name):
107         return self.factory(name)
108
109 class BuildDirTestCase(unittest.TestCase):
110     def runTest(self):
111         """Test build dir functionality"""
112         test=TestCmd(workdir='')
113
114         fs = SCons.Node.FS.FS()
115         f1 = fs.File('build/test1')
116         fs.BuildDir('build', 'src')
117         f2 = fs.File('build/test2')
118         d1 = fs.Dir('build')
119         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
120         assert f2.srcnode().path == os.path.normpath('src/test2'), f2.srcnode().path
121         assert d1.srcnode().path == 'src', d1.srcnode().path
122
123         fs = SCons.Node.FS.FS()
124         f1 = fs.File('build/test1')
125         fs.BuildDir('build', '.')
126         f2 = fs.File('build/test2')
127         d1 = fs.Dir('build')
128         assert f1.srcnode().path == 'test1', f1.srcnode().path
129         assert f2.srcnode().path == 'test2', f2.srcnode().path
130         assert d1.srcnode().path == '.', d1.srcnode().path
131
132         fs = SCons.Node.FS.FS()
133         fs.BuildDir('build/var1', 'src')
134         fs.BuildDir('build/var2', 'src')
135         f1 = fs.File('build/var1/test1')
136         f2 = fs.File('build/var2/test1')
137         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
138         assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path
139
140         fs = SCons.Node.FS.FS()
141         fs.BuildDir('../var1', 'src')
142         fs.BuildDir('../var2', 'src')
143         f1 = fs.File('../var1/test1')
144         f2 = fs.File('../var2/test1')
145         assert hasattr(f1, 'overrides')
146         assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
147         assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path
148
149         # Set up some files
150         test.subdir('work', ['work', 'src'])
151         test.subdir(['work', 'build'], ['work', 'build', 'var1'])
152         test.subdir(['work', 'build', 'var2'])
153         test.subdir('rep1', ['rep1', 'src'])
154         test.subdir(['rep1', 'build'], ['rep1', 'build', 'var1'])
155         test.subdir(['rep1', 'build', 'var2'])
156
157         # A source file in the source directory
158         test.write([ 'work', 'src', 'test.in' ], 'test.in')
159
160         # A source file in a subdir of the source directory
161         test.subdir([ 'work', 'src', 'new_dir' ])
162         test.write([ 'work', 'src', 'new_dir', 'test9.out' ], 'test9.out\n')
163
164         # A source file in the repository
165         test.write([ 'rep1', 'src', 'test2.in' ], 'test2.in')
166
167         # Some source files in the build directory
168         test.write([ 'work', 'build', 'var2', 'test.in' ], 'test.old')
169         test.write([ 'work', 'build', 'var2', 'test2.in' ], 'test2.old')
170
171         # An old derived file in the build directories
172         test.write([ 'work', 'build', 'var1', 'test.out' ], 'test.old')
173         test.write([ 'work', 'build', 'var2', 'test.out' ], 'test.old')
174
175         # And just in case we are weird, a derived file in the source
176         # dir.
177         test.write([ 'work', 'src', 'test.out' ], 'test.out.src')
178
179         # A derived file in the repository
180         test.write([ 'rep1', 'build', 'var1', 'test2.out' ], 'test2.out_rep')
181         test.write([ 'rep1', 'build', 'var2', 'test2.out' ], 'test2.out_rep')
182
183         os.chdir(test.workpath('work'))
184
185         fs = SCons.Node.FS.FS(test.workpath('work'))
186         fs.BuildDir('build/var1', 'src', duplicate=0)
187         fs.BuildDir('build/var2', 'src')
188         f1 = fs.File('build/var1/test.in')
189         f1out = fs.File('build/var1/test.out')
190         f1out.builder = 1
191         f1out_2 = fs.File('build/var1/test2.out')
192         f1out_2.builder = 1
193         f2 = fs.File('build/var2/test.in')
194         f2out = fs.File('build/var2/test.out')
195         f2out.builder = 1
196         f2out_2 = fs.File('build/var2/test2.out')
197         f2out_2.builder = 1
198         fs.Repository(test.workpath('rep1'))
199
200         assert f1.srcnode().path == os.path.normpath('src/test.in'),\
201                f1.srcnode().path
202         # str(node) returns source path for duplicate = 0
203         assert str(f1) == os.path.normpath('src/test.in'), str(f1)
204         # Build path does not exist
205         assert not f1.exists()
206         # ...but the actual file is not there...
207         assert not os.path.exists(f1.get_abspath())
208         # And duplicate=0 should also work just like a Repository
209         assert f1.rexists()
210         # rfile() should point to the source path
211         assert f1.rfile().path == os.path.normpath('src/test.in'),\
212                f1.rfile().path
213
214         assert f2.srcnode().path == os.path.normpath('src/test.in'),\
215                f2.srcnode().path
216         # str(node) returns build path for duplicate = 1
217         assert str(f2) == os.path.normpath('build/var2/test.in'), str(f2)
218         # Build path exists
219         assert f2.exists()
220         # ...and should copy the file from src to build path
221         assert test.read(['work', 'build', 'var2', 'test.in']) == 'test.in',\
222                test.read(['work', 'build', 'var2', 'test.in'])
223         # Since exists() is true, so should rexists() be
224         assert f2.rexists()
225
226         f3 = fs.File('build/var1/test2.in')
227         f4 = fs.File('build/var2/test2.in')
228
229         assert f3.srcnode().path == os.path.normpath('src/test2.in'),\
230                f3.srcnode().path
231         # str(node) returns source path for duplicate = 0
232         assert str(f3) == os.path.normpath('src/test2.in'), str(f3)
233         # Build path does not exist
234         assert not f3.exists()
235         # Source path does not either
236         assert not f3.srcnode().exists()
237         # But we do have a file in the Repository
238         assert f3.rexists()
239         # rfile() should point to the source path
240         assert f3.rfile().path == os.path.normpath(test.workpath('rep1/src/test2.in')),\
241                f3.rfile().path
242
243         assert f4.srcnode().path == os.path.normpath('src/test2.in'),\
244                f4.srcnode().path
245         # str(node) returns build path for duplicate = 1
246         assert str(f4) == os.path.normpath('build/var2/test2.in'), str(f4)
247         # Build path should exist
248         assert f4.exists()
249         # ...and copy over the file into the local build path
250         assert test.read(['work', 'build', 'var2', 'test2.in']) == 'test2.in'
251         # should exist in repository, since exists() is true
252         assert f4.rexists()
253         # rfile() should point to ourselves
254         assert f4.rfile().path == os.path.normpath('build/var2/test2.in'),\
255                f4.rfile().path
256
257         f5 = fs.File('build/var1/test.out')
258         f6 = fs.File('build/var2/test.out')
259
260         assert f5.exists()
261         # We should not copy the file from the source dir, since this is
262         # a derived file.
263         assert test.read(['work', 'build', 'var1', 'test.out']) == 'test.old'
264
265         assert f6.exists()
266         # We should not copy the file from the source dir, since this is
267         # a derived file.
268         assert test.read(['work', 'build', 'var2', 'test.out']) == 'test.old'
269
270         f7 = fs.File('build/var1/test2.out')
271         f8 = fs.File('build/var2/test2.out')
272
273         assert not f7.exists()
274         assert f7.rexists()
275         assert f7.rfile().path == os.path.normpath(test.workpath('rep1/build/var1/test2.out')),\
276                f7.rfile().path
277
278         assert not f8.exists()
279         assert f8.rexists()
280         assert f8.rfile().path == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\
281                f8.rfile().path
282
283         # Verify the Mkdir and Link actions are called
284         d9 = fs.Dir('build/var2/new_dir')
285         f9 = fs.File('build/var2/new_dir/test9.out')
286
287         class MkdirAction(Action):
288             def __init__(self, dir_made):
289                 self.dir_made = dir_made
290             def __call__(self, target, source, env, errfunc):
291                 self.dir_made.extend(target)
292
293         save_Link = SCons.Node.FS.Link
294         link_made = []
295         def link_func(target, source, env, link_made=link_made):
296             link_made.append(target)
297         SCons.Node.FS.Link = link_func
298
299         try:
300             dir_made = []
301             d9.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
302             f9.exists()
303             expect = os.path.join('build', 'var2', 'new_dir')
304             assert dir_made[0].path == expect, dir_made[0].path
305             expect = os.path.join('build', 'var2', 'new_dir', 'test9.out')
306             assert link_made[0].path == expect, link_made[0].path
307             assert f9.linked
308         finally:
309             SCons.Node.FS.Link = save_Link
310
311         # Test for an interesting pathological case...we have a source
312         # file in a build path, but not in a source path.  This can
313         # happen if you switch from duplicate=1 to duplicate=0, then
314         # delete a source file.  At one time, this would cause exists()
315         # to return a 1 but get_contents() to throw.
316         test.write([ 'work', 'build', 'var1', 'asourcefile' ], 'stuff')
317         f10 = fs.File('build/var1/asourcefile')
318         assert f10.exists()
319         assert f10.get_contents() == 'stuff', f10.get_contents()
320
321         f11 = fs.File('src/file11')
322         t, m = f11.alter_targets()
323         bdt = map(lambda n: n.path, t)
324         var1_file11 = os.path.normpath('build/var1/file11')
325         var2_file11 = os.path.normpath('build/var2/file11')
326         assert bdt == [var1_file11, var2_file11], bdt
327
328         f12 = fs.File('src/file12')
329         f12.builder = 1
330         bdt, m = f12.alter_targets()
331         assert bdt == [], map(lambda n: n.path, bdt)
332
333         d13 = fs.Dir('src/new_dir')
334         t, m = d13.alter_targets()
335         bdt = map(lambda n: n.path, t)
336         var1_new_dir = os.path.normpath('build/var1/new_dir')
337         var2_new_dir = os.path.normpath('build/var2/new_dir')
338         assert bdt == [var1_new_dir, var2_new_dir], bdt
339
340         # Test that an IOError trying to Link a src file
341         # into a BuildDir ends up throwing a StopError.
342         fIO = fs.File("build/var2/IOError")
343
344         save_Link = SCons.Node.FS.Link
345         def Link_IOError(target, source, env):
346             raise IOError, "Link_IOError"
347         SCons.Node.FS.Link = SCons.Action.Action(Link_IOError, None)
348
349         test.write(['work', 'src', 'IOError'], "work/src/IOError\n")
350
351         try:
352             exc_caught = 0
353             try:
354                 fIO.exists()
355             except SCons.Errors.StopError:
356                 exc_caught = 1
357             assert exc_caught, "Should have caught a StopError"
358
359         finally:
360             SCons.Node.FS.Link = save_Link
361
362         # Test to see if Link() works...
363         test.subdir('src','build')
364         test.write('src/foo', 'src/foo\n')
365         os.chmod(test.workpath('src/foo'), stat.S_IRUSR)
366         SCons.Node.FS.Link(fs.File(test.workpath('build/foo')),
367                            fs.File(test.workpath('src/foo')),
368                            None)
369         os.chmod(test.workpath('src/foo'), stat.S_IRUSR | stat.S_IWRITE)
370         st=os.stat(test.workpath('build/foo'))
371         assert (stat.S_IMODE(st[stat.ST_MODE]) & stat.S_IWRITE), \
372                stat.S_IMODE(st[stat.ST_MODE])
373
374         exc_caught = 0
375         try:
376             fs = SCons.Node.FS.FS()
377             fs.BuildDir('build', '/test/foo')
378         except SCons.Errors.UserError:
379             exc_caught = 1
380         assert exc_caught, "Should have caught a UserError."
381
382         exc_caught = 0
383         try:
384             try:
385                 fs = SCons.Node.FS.FS()
386                 fs.BuildDir('build', 'build/src')
387             except SCons.Errors.UserError:
388                 exc_caught = 1
389             assert exc_caught, "Should have caught a UserError."
390         finally:
391             test.unlink( "src/foo" )
392             test.unlink( "build/foo" )
393
394         fs = SCons.Node.FS.FS()
395         fs.BuildDir('build', 'src1')
396
397         # Calling the same BuildDir twice should work fine.
398         fs.BuildDir('build', 'src1')
399
400         # Trying to move a build dir to a second source dir
401         # should blow up
402         try:
403             fs.BuildDir('build', 'src2')
404         except SCons.Errors.UserError:
405             pass
406         else:
407             assert 0, "Should have caught a UserError."
408
409         # Test against a former bug.  Make sure we can get a repository
410         # path for the build directory itself!
411         fs=SCons.Node.FS.FS(test.workpath('work'))
412         test.subdir('work')
413         fs.BuildDir('build/var3', 'src', duplicate=0)
414         d1 = fs.Dir('build/var3')
415         assert d1.rdir() == fs.Dir('src'), str(d1.rdir())
416
417         # verify the link creation attempts in file_link()
418         class LinkSimulator :
419             """A class to intercept os.[sym]link() calls and track them."""
420
421             def __init__( self, duplicate ) :
422                 self.duplicate = duplicate
423                 self._reset()
424
425             def _reset( self ) :
426                 """Reset the simulator if necessary"""
427                 if not self._need_reset() : return # skip if not needed now
428                 self.links_to_be_called = self.duplicate
429
430             def _need_reset( self ) :
431                 """
432                 Determines whether or not the simulator needs to be reset.
433                 A reset is necessary if the object is first being initialized,
434                 or if all three methods have been tried already.
435                 """
436                 return (
437                         ( not hasattr( self , "links_to_be_called" ) )
438                         or
439                         (self.links_to_be_called == "")
440                        )
441
442             def link_fail( self , src , dest ) :
443                 self._reset()
444                 l = string.split(self.links_to_be_called, "-")
445                 next_link = l[0]
446                 assert  next_link == "hard", \
447                        "Wrong link order: expected %s to be called "\
448                        "instead of hard" % next_link
449                 self.links_to_be_called = string.join(l[1:], '-')
450                 raise OSError( "Simulating hard link creation error." )
451
452             def symlink_fail( self , src , dest ) :
453                 self._reset()
454                 l = string.split(self.links_to_be_called, "-")
455                 next_link = l[0]
456                 assert  next_link == "soft", \
457                        "Wrong link order: expected %s to be called "\
458                        "instead of soft" % next_link
459                 self.links_to_be_called = string.join(l[1:], '-')
460                 raise OSError( "Simulating symlink creation error." )
461
462             def copy( self , src , dest ) :
463                 self._reset()
464                 l = string.split(self.links_to_be_called, "-")
465                 next_link = l[0]
466                 assert  next_link == "copy", \
467                        "Wrong link order: expected %s to be called "\
468                        "instead of copy" % next_link
469                 self.links_to_be_called = string.join(l[1:], '-')
470                 # copy succeeds, but use the real copy
471                 self._real_copy(src, dest)
472         # end class LinkSimulator
473
474         try:
475             SCons.Node.FS.set_duplicate("no-link-order")
476             assert 0, "Expected exception when passing an invalid duplicate to set_duplicate"
477         except SCons.Errors.InternalError:
478             pass
479
480         for duplicate in SCons.Node.FS.Valid_Duplicates:
481             simulator = LinkSimulator(duplicate)
482
483             # save the real functions for later restoration
484             real_link = None
485             real_symlink = None
486             try:
487                 real_link = os.link
488             except AttributeError:
489                 pass
490             try:
491                 real_symlink = os.symlink
492             except AttributeError:
493                 pass
494             real_copy = shutil.copy2
495             simulator._real_copy = real_copy # the simulator needs the real one
496
497             # override the real functions with our simulation
498             os.link = simulator.link_fail
499             os.symlink = simulator.symlink_fail
500             shutil.copy2 = simulator.copy
501             SCons.Node.FS.set_duplicate(duplicate)
502
503             src_foo = test.workpath('src', 'foo')
504             build_foo = test.workpath('build', 'foo')
505
506             try:
507                 test.write(src_foo, 'src/foo\n')
508                 os.chmod(src_foo, stat.S_IRUSR)
509                 try:
510                     SCons.Node.FS.Link(fs.File(build_foo),
511                                        fs.File(src_foo),
512                                        None)
513                 finally:
514                     os.chmod(src_foo, stat.S_IRUSR | stat.S_IWRITE)
515                     test.unlink(src_foo)
516                     test.unlink(build_foo)
517
518             finally:
519                 # restore the real functions
520                 if real_link:
521                     os.link = real_link
522                 else:
523                     delattr(os, 'link')
524                 if real_symlink:
525                     os.symlink = real_symlink
526                 else:
527                     delattr(os, 'symlink')
528                 shutil.copy2 = real_copy
529
530 class FSTestCase(unittest.TestCase):
531     def runTest(self):
532         """Test FS (file system) Node operations
533
534         This test case handles all of the file system node
535         tests in one environment, so we don't have to set up a
536         complicated directory structure for each test individually.
537         """
538         test = TestCmd(workdir = '')
539         test.subdir('sub', ['sub', 'dir'])
540
541         wp = test.workpath('')
542         sub = test.workpath('sub', '')
543         sub_dir = test.workpath('sub', 'dir', '')
544         sub_dir_foo = test.workpath('sub', 'dir', 'foo', '')
545         sub_dir_foo_bar = test.workpath('sub', 'dir', 'foo', 'bar', '')
546         sub_foo = test.workpath('sub', 'foo', '')
547
548         os.chdir(sub_dir)
549
550         fs = SCons.Node.FS.FS()
551
552         e1 = fs.Entry('e1')
553         assert isinstance(e1, SCons.Node.FS.Entry)
554
555         d1 = fs.Dir('d1')
556         assert isinstance(d1, SCons.Node.FS.Dir)
557         assert d1.cwd is d1, d1
558
559         f1 = fs.File('f1', directory = d1)
560         assert isinstance(f1, SCons.Node.FS.File)
561
562         d1_f1 = os.path.join('d1', 'f1')
563         assert f1.path == d1_f1, "f1.path %s != %s" % (f1.path, d1_f1)
564         assert str(f1) == d1_f1, "str(f1) %s != %s" % (str(f1), d1_f1)
565
566         x1 = d1.File('x1')
567         assert isinstance(x1, SCons.Node.FS.File)
568         assert str(x1) == os.path.join('d1', 'x1')
569
570         x2 = d1.Dir('x2')
571         assert isinstance(x2, SCons.Node.FS.Dir)
572         assert str(x2) == os.path.join('d1', 'x2')
573
574         x3 = d1.Entry('x3')
575         assert isinstance(x3, SCons.Node.FS.Entry)
576         assert str(x3) == os.path.join('d1', 'x3')
577
578         assert d1.File(x1) == x1
579         assert d1.Dir(x2) == x2
580         assert d1.Entry(x3) == x3
581
582         x1.cwd = d1
583
584         x4 = x1.File('x4')
585         assert str(x4) == os.path.join('d1', 'x4')
586
587         x5 = x1.Dir('x5')
588         assert str(x5) == os.path.join('d1', 'x5')
589
590         x6 = x1.Entry('x6')
591         assert str(x6) == os.path.join('d1', 'x6')
592         x7 = x1.Entry('x7')
593         assert str(x7) == os.path.join('d1', 'x7')
594
595         assert x1.File(x4) == x4
596         assert x1.Dir(x5) == x5
597         assert x1.Entry(x6) == x6
598         assert x1.Entry(x7) == x7
599
600         assert x1.Entry(x5) == x5
601         try:
602             x1.File(x5)
603         except TypeError:
604             pass
605         else:
606             assert 0
607
608         assert x1.Entry(x4) == x4
609         try:
610             x1.Dir(x4)
611         except TypeError:
612             pass
613         else:
614             assert 0
615
616         x6 = x1.File(x6)
617         assert isinstance(x6, SCons.Node.FS.File)
618
619         x7 = x1.Dir(x7)
620         assert isinstance(x7, SCons.Node.FS.Dir)
621
622         seps = [os.sep]
623         if os.sep != '/':
624             seps = seps + ['/']
625
626         for sep in seps:
627
628             def Dir_test(lpath, path_, abspath_, up_path_, fileSys=fs, s=sep):
629                 dir = fileSys.Dir(string.replace(lpath, '/', s))
630
631                 if os.sep != '/':
632                     path_ = string.replace(path_, '/', os.sep)
633                     abspath_ = string.replace(abspath_, '/', os.sep)
634                     up_path_ = string.replace(up_path_, '/', os.sep)
635
636                 def strip_slash(p):
637                     if p[-1] == os.sep and len(p) > 1:
638                         p = p[:-1]
639                     return p
640                 path = strip_slash(path_)
641                 abspath = strip_slash(abspath_)
642                 up_path = strip_slash(up_path_)
643                 name = string.split(abspath, os.sep)[-1]
644
645                 assert dir.name == name, \
646                        "dir.name %s != expected name %s" % \
647                        (dir.name, name)
648                 assert dir.path == path, \
649                        "dir.path %s != expected path %s" % \
650                        (dir.path, path)
651                 assert str(dir) == path, \
652                        "str(dir) %s != expected path %s" % \
653                        (str(dir), path)
654                 assert dir.get_abspath() == abspath, \
655                        "dir.abspath %s != expected absolute path %s" % \
656                        (dir.get_abspath(), abspath)
657                 assert dir.up().path == up_path, \
658                        "dir.up().path %s != expected parent path %s" % \
659                        (dir.up().path, up_path)
660
661             Dir_test('foo',         'foo/',        sub_dir_foo,       './')
662             Dir_test('foo/bar',     'foo/bar/',    sub_dir_foo_bar,   'foo/')
663             Dir_test('/foo',        '/foo/',       '/foo/',           '/')
664             Dir_test('/foo/bar',    '/foo/bar/',   '/foo/bar/',       '/foo/')
665             Dir_test('..',          sub,           sub,               wp)
666             Dir_test('foo/..',      './',          sub_dir,           sub)
667             Dir_test('../foo',      sub_foo,       sub_foo,           sub)
668             Dir_test('.',           './',          sub_dir,           sub)
669             Dir_test('./.',         './',          sub_dir,           sub)
670             Dir_test('foo/./bar',   'foo/bar/',    sub_dir_foo_bar,   'foo/')
671             Dir_test('#../foo',     sub_foo,       sub_foo,           sub)
672             Dir_test('#/../foo',    sub_foo,       sub_foo,           sub)
673             Dir_test('#foo/bar',    'foo/bar/',    sub_dir_foo_bar,   'foo/')
674             Dir_test('#/foo/bar',   'foo/bar/',    sub_dir_foo_bar,   'foo/')
675             Dir_test('#',           './',          sub_dir,           sub)
676
677             try:
678                 f2 = fs.File(string.join(['f1', 'f2'], sep), directory = d1)
679             except TypeError, x:
680                 assert str(x) == ("Tried to lookup File '%s' as a Dir." %
681                                   d1_f1), x
682             except:
683                 raise
684
685             try:
686                 dir = fs.Dir(string.join(['d1', 'f1'], sep))
687             except TypeError, x:
688                 assert str(x) == ("Tried to lookup File '%s' as a Dir." %
689                                   d1_f1), x
690             except:
691                 raise
692
693             try:
694                 f2 = fs.File('d1')
695             except TypeError, x:
696                 assert str(x) == ("Tried to lookup Dir '%s' as a File." %
697                                   'd1'), x
698             except:
699                 raise
700
701             # Test that just specifying the drive works to identify
702             # its root directory.
703             p = os.path.abspath(test.workpath('root_file'))
704             drive, path = os.path.splitdrive(p)
705             if drive:
706                 # The assert below probably isn't correct for the
707                 # general case, but it works for Win32, which covers a
708                 # lot of ground...
709                 dir = fs.Dir(drive)
710                 assert str(dir) == drive + os.sep, str(dir)
711
712             # Test Dir.scan()
713             dir = fs.Dir('ddd')
714             fs.File(string.join(['ddd', 'f1'], sep))
715             fs.File(string.join(['ddd', 'f2'], sep))
716             fs.File(string.join(['ddd', 'f3'], sep))
717             fs.Dir(string.join(['ddd', 'd1'], sep))
718             fs.Dir(string.join(['ddd', 'd1', 'f4'], sep))
719             fs.Dir(string.join(['ddd', 'd1', 'f5'], sep))
720             dir.scan()
721             kids = map(lambda x: x.path, dir.children(None))
722             kids.sort()
723             assert kids == [os.path.join('ddd', 'd1'),
724                             os.path.join('ddd', 'f1'),
725                             os.path.join('ddd', 'f2'),
726                             os.path.join('ddd', 'f3')], kids
727
728         # Test for a bug in 0.04 that did not like looking up
729         # dirs with a trailing slash on Win32.
730         d=fs.Dir('./')
731         assert d.path == '.', d.abspath
732         d=fs.Dir('foo/')
733         assert d.path == 'foo', d.abspath
734
735         # Test for sub-classing of node building.
736         global built_it
737
738         built_it = None
739         assert not built_it
740         d1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
741         d1.builder_set(Builder(fs.File))
742         d1.env_set(Environment())
743         d1.build()
744         assert built_it
745
746         built_it = None
747         assert not built_it
748         f1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
749         f1.builder_set(Builder(fs.File))
750         f1.env_set(Environment())
751         f1.build()
752         assert built_it
753
754         def match(path, expect):
755             expect = string.replace(expect, '/', os.sep)
756             assert path == expect, "path %s != expected %s" % (path, expect)
757
758         e1 = fs.Entry("d1")
759         assert e1.__class__.__name__ == 'Dir'
760         match(e1.path, "d1")
761         match(e1.dir.path, ".")
762
763         e2 = fs.Entry("d1/f1")
764         assert e2.__class__.__name__ == 'File'
765         match(e2.path, "d1/f1")
766         match(e2.dir.path, "d1")
767
768         e3 = fs.Entry("e3")
769         assert e3.__class__.__name__ == 'Entry'
770         match(e3.path, "e3")
771         match(e3.dir.path, ".")
772
773         e4 = fs.Entry("d1/e4")
774         assert e4.__class__.__name__ == 'Entry'
775         match(e4.path, "d1/e4")
776         match(e4.dir.path, "d1")
777
778         e5 = fs.Entry("e3/e5")
779         assert e3.__class__.__name__ == 'Dir'
780         match(e3.path, "e3")
781         match(e3.dir.path, ".")
782         assert e5.__class__.__name__ == 'Entry'
783         match(e5.path, "e3/e5")
784         match(e5.dir.path, "e3")
785
786         e6 = fs.Dir("d1/e4")
787         assert e6 is e4
788         assert e4.__class__.__name__ == 'Dir'
789         match(e4.path, "d1/e4")
790         match(e4.dir.path, "d1")
791
792         e7 = fs.File("e3/e5")
793         assert e7 is e5
794         assert e5.__class__.__name__ == 'File'
795         match(e5.path, "e3/e5")
796         match(e5.dir.path, "e3")
797
798         fs.chdir(fs.Dir('subdir'))
799         f11 = fs.File("f11")
800         match(f11.path, "subdir/f11")
801         d12 = fs.Dir("d12")
802         e13 = fs.Entry("subdir/e13")
803         match(e13.path, "subdir/subdir/e13")
804         fs.chdir(fs.Dir('..'))
805
806         # Test scanning
807         f1.builder_set(Builder(fs.File))
808         f1.env_set(Environment())
809         xyz = fs.File("xyz")
810         f1.target_scanner = Scanner(xyz)
811
812         f1.scan()
813         assert f1.implicit[0].path == "xyz"
814         f1.implicit = []
815         f1.scan()
816         assert f1.implicit == []
817         f1.implicit = None
818         f1.scan()
819         assert f1.implicit[0].path == "xyz"
820
821         # Test underlying scanning functionality in get_found_includes()
822         env = Environment()
823         f12 = fs.File("f12")
824         t1 = fs.File("t1")
825
826         deps = f12.get_found_includes(env, None, t1)
827         assert deps == [], deps
828
829         class MyScanner(Scanner):
830             call_count = 0
831             def __call__(self, node, env, path):
832                 self.call_count = self.call_count + 1
833                 return Scanner.__call__(self, node, env, path)
834         s = MyScanner(xyz)
835
836         deps = f12.get_found_includes(env, s, t1)
837         assert deps == [xyz], deps
838         assert s.call_count == 1, s.call_count
839
840         deps = f12.get_found_includes(env, s, t1)
841         assert deps == [xyz], deps
842         assert s.call_count == 1, s.call_count
843
844         f12.built()
845
846         deps = f12.get_found_includes(env, s, t1)
847         assert deps == [xyz], deps
848         assert s.call_count == 2, s.call_count
849
850         # Make sure we can scan this file even if the target isn't
851         # a file that has a scanner (it might be an Alias, e.g.).
852         class DummyNode:
853             pass
854
855         deps = f12.get_found_includes(env, s, DummyNode())
856         assert deps == [xyz], deps
857
858         # Test building a file whose directory is not there yet...
859         f1 = fs.File(test.workpath("foo/bar/baz/ack"))
860         assert not f1.dir.exists()
861         f1.prepare()
862         f1.build()
863         assert f1.dir.exists()
864
865         os.chdir('..')
866
867         # Test getcwd()
868         fs = SCons.Node.FS.FS()
869         assert str(fs.getcwd()) == ".", str(fs.getcwd())
870         fs.chdir(fs.Dir('subdir'))
871         # The cwd's path is always "."
872         assert str(fs.getcwd()) == ".", str(fs.getcwd())
873         assert fs.getcwd().path == 'subdir', fs.getcwd().path
874         fs.chdir(fs.Dir('../..'))
875         assert fs.getcwd().path == test.workdir, fs.getcwd().path
876
877         f1 = fs.File(test.workpath("do_i_exist"))
878         assert not f1.exists()
879         test.write("do_i_exist","\n")
880         assert not f1.exists()
881         f1.built()
882         assert f1.exists()
883         test.unlink("do_i_exist")
884         assert f1.exists()
885         f1.built()
886         assert not f1.exists()
887
888         # For some reason, in Win32, the \x1a character terminates
889         # the reading of files in text mode.  This tests that
890         # get_contents() returns the binary contents.
891         test.write("binary_file", "Foo\x1aBar")
892         f1 = SCons.Node.FS.default_fs.File(test.workpath("binary_file"))
893         assert f1.get_contents() == "Foo\x1aBar", f1.get_contents()
894
895         def nonexistent(method, s):
896             try:
897                 x = method(s, create = 0)
898             except SCons.Errors.UserError:
899                 pass
900             else:
901                 raise TestFailed, "did not catch expected UserError"
902
903         nonexistent(fs.Entry, 'nonexistent')
904         nonexistent(fs.Entry, 'nonexistent/foo')
905
906         nonexistent(fs.File, 'nonexistent')
907         nonexistent(fs.File, 'nonexistent/foo')
908
909         nonexistent(fs.Dir, 'nonexistent')
910         nonexistent(fs.Dir, 'nonexistent/foo')
911
912         test.write("preserve_me", "\n")
913         assert os.path.exists(test.workpath("preserve_me"))
914         f1 = fs.File(test.workpath("preserve_me"))
915         f1.prepare()
916         assert os.path.exists(test.workpath("preserve_me"))
917
918         test.write("remove_me", "\n")
919         assert os.path.exists(test.workpath("remove_me"))
920         f1 = fs.File(test.workpath("remove_me"))
921         f1.builder = Builder(fs.File)
922         f1.env_set(Environment())
923         f1.prepare()
924         assert not os.path.exists(test.workpath("remove_me"))
925
926         e = fs.Entry('e_local')
927         assert not hasattr(e, '_local')
928         e.set_local()
929         assert e._local == 1
930         f = fs.File('e_local')
931         assert f._local == 1
932         f = fs.File('f_local')
933         assert f._local == 0
934
935         #XXX test current() for directories
936
937         #XXX test sconsign() for directories
938
939         #XXX test set_signature() for directories
940
941         #XXX test build() for directories
942
943         #XXX test root()
944
945         # test Entry.get_contents()
946         e = fs.Entry('does_not_exist')
947         exc_caught = 0
948         try:
949             e.get_contents()
950         except AttributeError:
951             exc_caught = 1
952         assert exc_caught, "Should have caught an AttributError"
953
954         test.write("file", "file\n")
955         try:
956             e = fs.Entry('file')
957             c = e.get_contents()
958             assert c == "file\n", c
959             assert e.__class__ == SCons.Node.FS.File
960         finally:
961             test.unlink("file")
962
963         test.subdir("dir")
964         e = fs.Entry('dir')
965         c = e.get_contents()
966         assert c == "", c
967         assert e.__class__ == SCons.Node.FS.Dir
968
969         if hasattr(os, 'symlink'):
970             os.symlink('nonexistent', test.workpath('dangling_symlink'))
971             e = fs.Entry('dangling_symlink')
972             c = e.get_contents()
973             assert e.__class__ == SCons.Node.FS.Entry
974             assert c == "", c
975
976         test.write("tstamp", "tstamp\n")
977         try:
978             # Okay, *this* manipulation accomodates Windows FAT file systems
979             # that only have two-second granularity on their timestamps.
980             # We round down the current time to the nearest even integer
981             # value, subtract two to make sure the timestamp is not "now,"
982             # and then convert it back to a float.
983             tstamp = float(int(time.time() / 2) * 2) - 2
984             os.utime(test.workpath("tstamp"), (tstamp - 2.0, tstamp))
985             f = fs.File("tstamp")
986             t = f.get_timestamp()
987             assert t == tstamp, "expected %f, got %f" % (tstamp, t)
988         finally:
989             test.unlink("tstamp")
990
991         test.subdir('tdir1')
992         d = fs.Dir('tdir1')
993         t = d.get_timestamp()
994         assert t == 0, "expected 0, got %s" % str(t)
995
996         test.subdir('tdir2')
997         d = fs.Dir('tdir2')
998         f1 = test.workpath('tdir2', 'file1')
999         f2 = test.workpath('tdir2', 'file2')
1000         test.write(f1, 'file1\n')
1001         test.write(f2, 'file2\n')
1002         fs.File(f1)
1003         fs.File(f2)
1004         current_time = float(int(time.time() / 2) * 2)
1005         t1 = current_time - 4.0
1006         t2 = current_time - 2.0
1007         os.utime(f1, (t1 - 2.0, t1))
1008         os.utime(f2, (t2 - 2.0, t2))
1009         t = d.get_timestamp()
1010         assert t == t2, "expected %f, got %f" % (t2, t)
1011
1012         skey = fs.Entry('eee.x').scanner_key()
1013         assert skey == '.x', skey
1014         skey = fs.Entry('eee.xyz').scanner_key()
1015         assert skey == '.xyz', skey
1016
1017         skey = fs.File('fff.x').scanner_key()
1018         assert skey == '.x', skey
1019         skey = fs.File('fff.xyz').scanner_key()
1020         assert skey == '.xyz', skey
1021
1022         skey = fs.Dir('ddd.x').scanner_key()
1023         assert skey is None, skey
1024
1025         test.write("i_am_not_a_directory", "\n")
1026         try:
1027             exc_caught = 0
1028             try:
1029                 fs.Dir(test.workpath("i_am_not_a_directory"))
1030             except TypeError:
1031                 exc_caught = 1
1032             assert exc_caught, "Should have caught a TypeError"
1033         finally:
1034             test.unlink("i_am_not_a_directory")
1035
1036         exc_caught = 0
1037         try:
1038             fs.File(sub_dir)
1039         except TypeError:
1040             exc_caught = 1
1041         assert exc_caught, "Should have caught a TypeError"
1042
1043         # XXX test calc_signature()
1044
1045         # XXX test current()
1046
1047         d = fs.Dir('dir')
1048         r = d.remove()
1049         assert r is None, r
1050
1051         f = fs.File('does_not_exist')
1052         r = f.remove()
1053         assert r == None, r
1054
1055         test.write('exists', "exists\n")
1056         f = fs.File('exists')
1057         r = f.remove()
1058         assert r, r
1059         assert not os.path.exists(test.workpath('exists')), "exists was not removed"
1060
1061         symlink = test.workpath('symlink')
1062         try:
1063             os.symlink(test.workpath('does_not_exist'), symlink)
1064             assert os.path.islink(symlink)
1065             f = fs.File('symlink')
1066             r = f.remove()
1067             assert r, r
1068             assert not os.path.islink(symlink), "symlink was not removed"
1069         except AttributeError:
1070             pass
1071
1072         test.write('can_not_remove', "can_not_remove\n")
1073         test.writable(test.workpath('.'), 0)
1074         fp = open(test.workpath('can_not_remove'))
1075
1076         f = fs.File('can_not_remove')
1077         exc_caught = 0
1078         try:
1079             r = f.remove()
1080         except OSError:
1081             exc_caught = 1
1082
1083         fp.close()
1084
1085         assert exc_caught, "Should have caught an OSError, r = " + str(r)
1086
1087         f = fs.Entry('foo/bar/baz')
1088         assert f.for_signature() == 'baz', f.for_signature()
1089         assert f.get_string(0) == os.path.normpath('foo/bar/baz'), \
1090                f.get_string(0)
1091         assert f.get_string(1) == 'baz', f.get_string(1)
1092
1093         x = fs.File('x.c')
1094         t = x.target_from_source('pre-', '-suf')
1095         assert str(t) == 'pre-x-suf', str(t)
1096
1097         y = fs.File('dir/y')
1098         t = y.target_from_source('pre-', '-suf')
1099         assert str(t) == os.path.join('dir', 'pre-y-suf'), str(t)
1100
1101         z = fs.File('zz')
1102         t = z.target_from_source('pre-', '-suf', lambda x: x[:-1])
1103         assert str(t) == 'pre-z-suf', str(t)
1104
1105 class EntryTestCase(unittest.TestCase):
1106     def runTest(self):
1107         """Test methods specific to the Entry sub-class.
1108         """
1109         test = TestCmd(workdir='')
1110         # FS doesn't like the cwd to be something other than its root.
1111         os.chdir(test.workpath(""))
1112
1113         fs = SCons.Node.FS.FS()
1114
1115         e1 = fs.Entry('e1')
1116         e1.rfile()
1117         assert e1.__class__ is SCons.Node.FS.File, e1.__class__
1118
1119         e2 = fs.Entry('e2')
1120         e2.get_found_includes(None, None, None)
1121         assert e2.__class__ is SCons.Node.FS.File, e2.__class__
1122
1123         test.subdir('e3d')
1124         test.write('e3f', "e3f\n")
1125
1126         e3d = fs.Entry('e3d')
1127         e3d.get_contents()
1128         assert e3d.__class__ is SCons.Node.FS.Dir, e3d.__class__
1129
1130         e3f = fs.Entry('e3f')
1131         e3f.get_contents()
1132         assert e3f.__class__ is SCons.Node.FS.File, e3f.__class__
1133
1134         e3n = fs.Entry('e3n')
1135         exc_caught = None
1136         try:
1137             e3n.get_contents()
1138         except AttributeError:
1139             exc_caught = 1
1140         assert exc_caught, "did not catch expected AttributeError"
1141
1142         test.subdir('e4d')
1143         test.write('e4f', "e4f\n")
1144
1145         e4d = fs.Entry('e4d')
1146         exists = e4d.exists()
1147         assert e4d.__class__ is SCons.Node.FS.Dir, e4d.__class__
1148         assert exists, "e4d does not exist?"
1149
1150         e4f = fs.Entry('e4f')
1151         exists = e4f.exists()
1152         assert e4f.__class__ is SCons.Node.FS.File, e4f.__class__
1153         assert exists, "e4f does not exist?"
1154
1155         e4n = fs.Entry('e4n')
1156         exists = e4n.exists()
1157         assert e4n.__class__ is SCons.Node.FS.File, e4n.__class__
1158         assert not exists, "e4n exists?"
1159
1160         class MyCalc:
1161             def __init__(self, val):
1162                 self.max_drift = 0
1163                 class M:
1164                     def __init__(self, val):
1165                         self.val = val
1166                     def collect(self, args):
1167                         return reduce(lambda x, y: x+y, args)
1168                     def signature(self, executor):
1169                         return self.val + 222
1170                 self.module = M(val)
1171
1172         test.subdir('e5d')
1173         test.write('e5f', "e5f\n")
1174
1175         e5d = fs.Entry('e5d')
1176         sig = e5d.calc_signature(MyCalc(555))
1177         assert e5d.__class__ is SCons.Node.FS.Dir, e5d.__class__
1178         assert sig == 777, sig
1179
1180         e5f = fs.Entry('e5f')
1181         sig = e5f.calc_signature(MyCalc(666))
1182         assert e5f.__class__ is SCons.Node.FS.File, e5f.__class__
1183         assert sig == 888, sig
1184
1185         e5n = fs.Entry('e5n')
1186         sig = e5n.calc_signature(MyCalc(777))
1187         assert e5n.__class__ is SCons.Node.FS.File, e5n.__class__
1188         assert sig is None, sig
1189
1190
1191
1192 class RepositoryTestCase(unittest.TestCase):
1193     def runTest(self):
1194         """Test FS (file system) Repository operations
1195
1196         """
1197         fs = SCons.Node.FS.FS()
1198
1199         fs.Repository('foo')
1200         fs.Repository(os.path.join('foo', 'bar'))
1201         fs.Repository(os.path.join('bar', 'foo'))
1202         fs.Repository('bar')
1203
1204         rep = fs.Dir('#').getRepositories()
1205         assert len(rep) == 4, map(str, rep)
1206         r = map(lambda x, np=os.path.normpath: np(str(x)), rep)
1207         assert r == ['foo',
1208                      os.path.join('foo', 'bar'),
1209                      os.path.join('bar', 'foo'),
1210                      'bar'], r
1211
1212         test = TestCmd(workdir = '')
1213         test.subdir('rep1', 'rep2', 'rep3', 'work')
1214
1215         rep1 = test.workpath('rep1')
1216         rep2 = test.workpath('rep2')
1217         rep3 = test.workpath('rep3')
1218
1219         os.chdir(test.workpath('work'))
1220
1221         fs = SCons.Node.FS.FS()
1222         fs.Repository(rep1, rep2, rep3)
1223
1224         f1 = fs.File(os.path.join('f1'))
1225         assert f1.rfile() is f1
1226
1227         test.write([rep1, 'f2'], "")
1228
1229         f2 = fs.File('f2')
1230         assert not f2.rfile() is f2, f2.rfile()
1231         assert str(f2.rfile()) == os.path.join(rep1, 'f2'), str(f2.rfile())
1232
1233         test.subdir([rep2, 'f3'])
1234         test.write([rep3, 'f3'], "")
1235
1236         f3 = fs.File('f3')
1237         assert not f3.rfile() is f3, f3.rfile()
1238         assert f3.rstr() == os.path.join(rep3, 'f3'), f3.rstr()
1239
1240         assert fs.Rsearch('f1') is None
1241         assert fs.Rsearch('f2')
1242         assert fs.Rsearch(f3) is f3
1243
1244         list = fs.Rsearchall(fs.Dir('d1'))
1245         assert len(list) == 1, list
1246         assert list[0].path == 'd1', list[0].path
1247
1248         list = fs.Rsearchall([fs.Dir('d1')])
1249         assert len(list) == 1, list
1250         assert list[0].path == 'd1', list[0].path
1251
1252         list = fs.Rsearchall('d2')
1253         assert list == [], list
1254
1255         list = fs.Rsearchall('#d2')
1256         assert list == [], list
1257
1258         test.subdir(['work', 'd2'])
1259         fs.File('d2').built() # Clear exists cache
1260         list = fs.Rsearchall('d2')
1261         assert map(str, list) == ['d2'], list
1262
1263         test.subdir(['rep2', 'd2'])
1264         fs.File('../rep2/d2').built() # Clear exists cache
1265         list = fs.Rsearchall('d2')
1266         assert map(str, list) == ['d2', test.workpath('rep2', 'd2')], list
1267
1268         test.subdir(['rep1', 'd2'])
1269         fs.File('../rep1/d2').built() # Clear exists cache
1270         list = fs.Rsearchall('d2')
1271         assert map(str, list) == ['d2',
1272                                   test.workpath('rep1', 'd2'),
1273                                   test.workpath('rep2', 'd2')], list
1274
1275         list = fs.Rsearchall(['d3', 'd4'])
1276         assert list == [], list
1277
1278         test.subdir(['work', 'd3'])
1279         fs.File('d3').built() # Clear exists cache
1280         list = map(str, fs.Rsearchall(['d3', 'd4']))
1281         assert list == ['d3'], list
1282
1283         test.subdir(['rep3', 'd4'])
1284         fs.File('../rep3/d4').built() # Clear exists cache
1285         list = map(str, fs.Rsearchall(['d3', 'd4']))
1286         assert list == ['d3', test.workpath('rep3', 'd4')], list
1287
1288         list = map(str, fs.Rsearchall(string.join(['d3', 'd4'], os.pathsep)))
1289         assert list == ['d3', test.workpath('rep3', 'd4')], list
1290
1291         work_d4 = fs.File(os.path.join('work', 'd4'))
1292         list = map(str, fs.Rsearchall(['d3', work_d4]))
1293         assert list == ['d3', str(work_d4)], list
1294
1295         list = fs.Rsearchall('')
1296         assert list == [], list
1297
1298         list = fs.Rsearchall([None])
1299         assert list == [], list
1300
1301         list = fs.Rsearchall([''])
1302         assert list == [], list
1303
1304         fs.BuildDir('build', '.')
1305
1306         f = fs.File(test.workpath("work", "i_do_not_exist"))
1307         assert not f.rexists()
1308
1309         test.write(["rep2", "i_exist"], "\n")
1310         f = fs.File(test.workpath("work", "i_exist"))
1311         assert f.rexists()
1312
1313         test.write(["work", "i_exist_too"], "\n")
1314         f = fs.File(test.workpath("work", "i_exist_too"))
1315         assert f.rexists()
1316
1317         f1 = fs.File(os.path.join('build', 'f1'))
1318         assert not f1.rexists()
1319
1320         f2 = fs.File(os.path.join('build', 'f2'))
1321         assert f2.rexists()
1322
1323         test.write(["rep2", "tstamp"], "tstamp\n")
1324         try:
1325             # Okay, *this* manipulation accomodates Windows FAT file systems
1326             # that only have two-second granularity on their timestamps.
1327             # We round down the current time to the nearest even integer
1328             # value, subtract two to make sure the timestamp is not "now,"
1329             # and then convert it back to a float.
1330             tstamp = float(int(time.time() / 2) * 2) - 2
1331             os.utime(test.workpath("rep2", "tstamp"), (tstamp - 2.0, tstamp))
1332             f = fs.File("tstamp")
1333             t = f.get_timestamp()
1334             assert t == tstamp, "expected %f, got %f" % (tstamp, t)
1335         finally:
1336             test.unlink(["rep2", "tstamp"])
1337
1338         # Make sure get_contents() returns the binary contents.
1339         test.write(["rep3", "contents"], "Con\x1aTents\n")
1340         try:
1341             c = fs.File("contents").get_contents()
1342             assert c == "Con\x1aTents\n", "got '%s'" % c
1343         finally:
1344             test.unlink(["rep3", "contents"])
1345
1346         # XXX test calc_signature()
1347
1348         # XXX test current()
1349
1350 class find_fileTestCase(unittest.TestCase):
1351     def runTest(self):
1352         """Testing find_file function"""
1353         test = TestCmd(workdir = '')
1354         test.write('./foo', 'Some file\n')
1355         fs = SCons.Node.FS.FS(test.workpath(""))
1356         os.chdir(test.workpath("")) # FS doesn't like the cwd to be something other than it's root
1357         node_derived = fs.File(test.workpath('bar/baz'))
1358         node_derived.builder_set(1) # Any non-zero value.
1359         node_pseudo = fs.File(test.workpath('pseudo'))
1360         node_pseudo.set_src_builder(1) # Any non-zero value.
1361         paths = map(fs.Dir, ['.', './bar'])
1362         nodes = [SCons.Node.FS.find_file('foo', paths, fs.File),
1363                  SCons.Node.FS.find_file('baz', paths, fs.File),
1364                  SCons.Node.FS.find_file('pseudo', paths, fs.File)]
1365         file_names = map(str, nodes)
1366         file_names = map(os.path.normpath, file_names)
1367         assert os.path.normpath('./foo') in file_names, file_names
1368         assert os.path.normpath('./bar/baz') in file_names, file_names
1369         assert os.path.normpath('./pseudo') in file_names, file_names
1370
1371 class StringDirTestCase(unittest.TestCase):
1372     def runTest(self):
1373         """Test using a string as the second argument of
1374         File() and Dir()"""
1375
1376         test = TestCmd(workdir = '')
1377         test.subdir('sub')
1378         fs = SCons.Node.FS.FS(test.workpath(''))
1379
1380         d = fs.Dir('sub', '.')
1381         assert str(d) == 'sub'
1382         assert d.exists()
1383         f = fs.File('file', 'sub')
1384         assert str(f) == os.path.join('sub', 'file')
1385         assert not f.exists()
1386
1387 class stored_infoTestCase(unittest.TestCase):
1388     def runTest(self):
1389         """Test how storing build information"""
1390         test = TestCmd(workdir = '')
1391         test.subdir('sub')
1392         fs = SCons.Node.FS.FS(test.workpath(''))
1393
1394         d = fs.Dir('sub')
1395         f = fs.File('file1', d)
1396         bi = f.get_stored_info()
1397         assert bi.bsig == None, bi.bsig
1398
1399         class MySConsign:
1400             class Null:
1401                 def __init__(self):
1402                     self.xyzzy = 7
1403             def get_entry(self, name):
1404                 return self.Null()
1405
1406         f = fs.File('file2', d)
1407         f.dir.sconsign = MySConsign
1408         bi = f.get_stored_info()
1409         assert bi.xyzzy == 7, bi
1410
1411 class has_src_builderTestCase(unittest.TestCase):
1412     def runTest(self):
1413         """Test the has_src_builder() method"""
1414         test = TestCmd(workdir = '')
1415         fs = SCons.Node.FS.FS(test.workpath(''))
1416         os.chdir(test.workpath(''))
1417         test.subdir('sub1')
1418         test.subdir('sub2', ['sub2', 'SCCS'], ['sub2', 'RCS'])
1419
1420         sub1 = fs.Dir('sub1', '.')
1421         f1 = fs.File('f1', sub1)
1422         f2 = fs.File('f2', sub1)
1423         f3 = fs.File('f3', sub1)
1424         sub2 = fs.Dir('sub2', '.')
1425         f4 = fs.File('f4', sub2)
1426         f5 = fs.File('f5', sub2)
1427         f6 = fs.File('f6', sub2)
1428         f7 = fs.File('f7', sub2)
1429         f8 = fs.File('f8', sub2)
1430
1431         h = f1.has_src_builder()
1432         assert not h, h
1433         h = f1.has_builder()
1434         assert not h, h
1435
1436         b1 = Builder(fs.File)
1437         sub1.set_src_builder(b1)
1438
1439         test.write(['sub1', 'f2'], "sub1/f2\n")
1440         h = f1.has_src_builder()        # cached from previous call
1441         assert not h, h
1442         h = f1.has_builder()            # cached from previous call
1443         assert not h, h
1444         h = f2.has_src_builder()
1445         assert not h, h
1446         h = f2.has_builder()
1447         assert not h, h
1448         h = f3.has_src_builder()
1449         assert h, h
1450         h = f3.has_builder()
1451         assert h, h
1452         assert f3.builder is b1, f3.builder
1453
1454         f7.set_src_builder(b1)
1455         f8.builder_set(b1)
1456
1457         test.write(['sub2', 'SCCS', 's.f5'], "sub2/SCCS/s.f5\n")
1458         test.write(['sub2', 'RCS', 'f6,v'], "sub2/RCS/f6,v\n")
1459         h = f4.has_src_builder()
1460         assert not h, h
1461         h = f4.has_builder()
1462         assert not h, h
1463         h = f5.has_src_builder()
1464         assert h, h
1465         h = f5.has_builder()
1466         assert h, h
1467         h = f6.has_src_builder()
1468         assert h, h
1469         h = f6.has_builder()
1470         assert h, h
1471         h = f7.has_src_builder()
1472         assert h, h
1473         h = f7.has_builder()
1474         assert h, h
1475         h = f8.has_src_builder()
1476         assert not h, h
1477         h = f8.has_builder()
1478         assert h, h
1479
1480 class prepareTestCase(unittest.TestCase):
1481     def runTest(self):
1482         """Test the prepare() method"""
1483
1484         class MyFile(SCons.Node.FS.File):
1485             def _createDir(self):
1486                 raise SCons.Errors.StopError
1487             def exists(self):
1488                 return None
1489
1490         fs = SCons.Node.FS.FS()
1491         file = MyFile('foo', fs.Dir('.'), fs)
1492
1493         exc_caught = 0
1494         try:
1495             file.prepare()
1496         except SCons.Errors.StopError:
1497             exc_caught = 1
1498         assert exc_caught, "Should have caught a StopError."
1499
1500         class MkdirAction(Action):
1501             def __init__(self, dir_made):
1502                 self.dir_made = dir_made
1503             def __call__(self, target, source, env, errfunc):
1504                 self.dir_made.extend(target)
1505
1506         dir_made = []
1507         new_dir = fs.Dir("new_dir")
1508         new_dir.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
1509         xyz = fs.File(os.path.join("new_dir", "xyz"))
1510
1511         xyz.set_state(SCons.Node.up_to_date)
1512         xyz.prepare()
1513         assert dir_made == [], dir_made
1514         xyz.set_state(0)
1515         xyz.prepare()
1516         assert dir_made[0].path == "new_dir", dir_made[0]
1517
1518         dir = fs.Dir("dir")
1519         dir.prepare()
1520
1521 class get_actionsTestCase(unittest.TestCase):
1522     def runTest(self):
1523         """Test the Dir's get_action() method"""
1524
1525         fs = SCons.Node.FS.FS()
1526         dir = fs.Dir('.')
1527         a = dir.get_actions()
1528         assert a == [], a
1529
1530 class SConstruct_dirTestCase(unittest.TestCase):
1531     def runTest(self):
1532         """Test setting the SConstruct directory"""
1533
1534         fs = SCons.Node.FS.FS()
1535         fs.set_SConstruct_dir(fs.Dir('xxx'))
1536         assert fs.SConstruct_dir.path == 'xxx'
1537
1538 class CacheDirTestCase(unittest.TestCase):
1539     def runTest(self):
1540         """Test CacheDir functionality"""
1541         test = TestCmd(workdir='')
1542
1543         global built_it
1544
1545         fs = SCons.Node.FS.FS()
1546         assert fs.CachePath is None, fs.CachePath
1547         assert fs.cache_force is None, fs.cache_force
1548         assert fs.cache_show is None, fs.cache_show
1549
1550         fs.CacheDir('cache')
1551         assert fs.CachePath == 'cache', fs.CachePath
1552
1553         save_CacheRetrieve = SCons.Node.FS.CacheRetrieve
1554         self.retrieved = []
1555         def retrieve_succeed(target, source, env, self=self, execute=1):
1556             self.retrieved.append(target)
1557             return 0
1558         def retrieve_fail(target, source, env, self=self, execute=1):
1559             self.retrieved.append(target)
1560             return 1
1561
1562         f1 = fs.File("cd.f1")
1563         f1.builder_set(Builder(fs.File))
1564         f1.env_set(Environment())
1565         try:
1566             SCons.Node.FS.CacheRetrieve = retrieve_succeed
1567             self.retrieved = []
1568             built_it = None
1569
1570             r = f1.retrieve_from_cache()
1571             assert r == 1, r
1572             assert self.retrieved == [f1], self.retrieved
1573             assert built_it is None, built_it
1574
1575             SCons.Node.FS.CacheRetrieve = retrieve_fail
1576             self.retrieved = []
1577             built_it = None
1578
1579             r = f1.retrieve_from_cache()
1580             assert r is None, r
1581             assert self.retrieved == [f1], self.retrieved
1582             assert built_it is None, built_it
1583         finally:
1584             SCons.Node.FS.CacheRetrieve = save_CacheRetrieve
1585
1586         save_CacheRetrieveSilent = SCons.Node.FS.CacheRetrieveSilent
1587
1588         fs.cache_show = 1
1589
1590         f2 = fs.File("cd.f2")
1591         f2.builder_set(Builder(fs.File))
1592         f2.env_set(Environment())
1593         try:
1594             SCons.Node.FS.CacheRetrieveSilent = retrieve_succeed
1595             self.retrieved = []
1596             built_it = None
1597
1598             r = f2.retrieve_from_cache()
1599             assert r == 1, r
1600             assert self.retrieved == [f2], self.retrieved
1601             assert built_it is None, built_it
1602
1603             SCons.Node.FS.CacheRetrieveSilent = retrieve_fail
1604             self.retrieved = []
1605             built_it = None
1606
1607             r = f2.retrieve_from_cache()
1608             assert r is None, r
1609             assert self.retrieved == [f2], self.retrieved
1610             assert built_it is None, built_it
1611         finally:
1612             SCons.Node.FS.CacheRetrieveSilent = save_CacheRetrieveSilent
1613
1614         save_CachePush = SCons.Node.FS.CachePush
1615         def push(target, source, env, self=self):
1616             self.pushed.append(target)
1617             return 0
1618         SCons.Node.FS.CachePush = push
1619
1620         try:
1621             self.pushed = []
1622
1623             cd_f3 = test.workpath("cd.f3")
1624             f3 = fs.File(cd_f3)
1625             f3.built()
1626             assert self.pushed == [], self.pushed
1627             test.write(cd_f3, "cd.f3\n")
1628             f3.built()
1629             assert self.pushed == [f3], self.pushed
1630
1631             self.pushed = []
1632
1633             cd_f4 = test.workpath("cd.f4")
1634             f4 = fs.File(cd_f4)
1635             f4.visited()
1636             assert self.pushed == [], self.pushed
1637             test.write(cd_f4, "cd.f4\n")
1638             f4.visited()
1639             assert self.pushed == [], self.pushed
1640             fs.cache_force = 1
1641             f4.visited()
1642             assert self.pushed == [f4], self.pushed
1643         finally:
1644             SCons.Node.FS.CachePush = save_CachePush
1645
1646         # Verify how the cachepath() method determines the name
1647         # of the file in cache.
1648         def my_collect(list):
1649             return list[0]
1650         save_collect = SCons.Sig.MD5.collect
1651         SCons.Sig.MD5.collect = my_collect
1652         try:
1653             f5 = fs.File("cd.f5")
1654             f5.binfo = f5.new_binfo()
1655             f5.binfo.bsig = 'a_fake_bsig'
1656             cp = f5.cachepath()
1657             dirname = os.path.join('cache', 'A')
1658             filename = os.path.join(dirname, 'a_fake_bsig')
1659             assert cp == (dirname, filename), cp
1660         finally:
1661             SCons.Sig.MD5.collect = save_collect
1662
1663         # Verify that no bsig raises an InternalERror
1664         f6 = fs.File("cd.f6")
1665         f6.binfo = f6.new_binfo()
1666         exc_caught = 0
1667         try:
1668             cp = f6.cachepath()
1669         except SCons.Errors.InternalError:
1670             exc_caught = 1
1671         assert exc_caught
1672
1673         # Verify that we raise a warning if we can't copy a file to cache.
1674         save_copy2 = shutil.copy2
1675         def copy2(src, dst):
1676             raise OSError
1677         shutil.copy2 = copy2
1678         save_mkdir = os.mkdir
1679         def mkdir(dir, mode=0):
1680             pass
1681         os.mkdir = mkdir
1682         old_warn_exceptions = SCons.Warnings.warningAsException(1)
1683         SCons.Warnings.enableWarningClass(SCons.Warnings.CacheWriteErrorWarning)
1684
1685         try:
1686             cd_f7 = test.workpath("cd.f7")
1687             test.write(cd_f7, "cd.f7\n")
1688             f7 = fs.File(cd_f7)
1689             f7.binfo = f7.new_binfo()
1690             f7.binfo.bsig = 'f7_bsig'
1691
1692             warn_caught = 0
1693             try:
1694                 f7.built()
1695             except SCons.Warnings.CacheWriteErrorWarning:
1696                 warn_caught = 1
1697             assert warn_caught
1698         finally:
1699             shutil.copy2 = save_copy2
1700             os.mkdir = save_mkdir
1701             SCons.Warnings.warningAsException(old_warn_exceptions)
1702             SCons.Warnings.suppressWarningClass(SCons.Warnings.CacheWriteErrorWarning)
1703
1704         # Verify that we don't blow up if there's no strfunction()
1705         # for an action.
1706         act = Action()
1707         act.strfunction = None
1708         f8 = fs.File("cd.f8")
1709         f8.builder_set(Builder(fs.File, action=act))
1710         f8.env_set(Environment())
1711         try:
1712             SCons.Node.FS.CacheRetrieveSilent = retrieve_succeed
1713             self.retrieved = []
1714             built_it = None
1715
1716             r = f8.retrieve_from_cache()
1717             assert r == 1, r
1718             assert self.retrieved == [f8], self.retrieved
1719             assert built_it is None, built_it
1720
1721             SCons.Node.FS.CacheRetrieveSilent = retrieve_fail
1722             self.retrieved = []
1723             built_it = None
1724
1725             r = f8.retrieve_from_cache()
1726             assert r is None, r
1727             assert self.retrieved == [f8], self.retrieved
1728             assert built_it is None, built_it
1729         finally:
1730             SCons.Node.FS.CacheRetrieveSilent = save_CacheRetrieveSilent
1731
1732 class clearTestCase(unittest.TestCase):
1733     def runTest(self):
1734         """Test clearing FS nodes of cached data."""
1735         fs = SCons.Node.FS.FS()
1736
1737         e = fs.Entry('e')
1738         e._exists = 1
1739         e._rexists = 1
1740         e._str_val = 'e'
1741         e.clear()
1742         assert not hasattr(e, '_exists')
1743         assert not hasattr(e, '_rexists')
1744         assert not hasattr(e, '_str_val')
1745
1746         d = fs.Dir('d')
1747         d._exists = 1
1748         d._rexists = 1
1749         d._str_val = 'd'
1750         d.clear()
1751         assert not hasattr(d, '_exists')
1752         assert not hasattr(d, '_rexists')
1753         assert not hasattr(d, '_str_val')
1754
1755         f = fs.File('f')
1756         f._exists = 1
1757         f._rexists = 1
1758         f._str_val = 'f'
1759         f.clear()
1760         assert not hasattr(f, '_exists')
1761         assert not hasattr(f, '_rexists')
1762         assert not hasattr(f, '_str_val')
1763
1764 class postprocessTestCase(unittest.TestCase):
1765     def runTest(self):
1766         """Test calling the postprocess() method."""
1767         fs = SCons.Node.FS.FS()
1768
1769         e = fs.Entry('e')
1770         e.postprocess()
1771
1772         d = fs.Dir('d')
1773         d.postprocess()
1774
1775         f = fs.File('f')
1776         f.postprocess()
1777
1778 class SpecialAttrTestCase(unittest.TestCase):
1779     def runTest(self):
1780         """Test special attributes of file nodes."""
1781         test=TestCmd(workdir='')
1782         fs = SCons.Node.FS.FS(test.workpath('work'))
1783
1784         f = fs.Entry('foo/bar/baz.blat').get_subst_proxy()
1785
1786         s = str(f.dir)
1787         assert s == os.path.normpath('foo/bar'), s
1788         assert f.dir.is_literal(), f.dir
1789         for_sig = f.dir.for_signature()
1790         assert for_sig == 'bar', for_sig
1791
1792         s = str(f.file)
1793         assert s == 'baz.blat', s
1794         assert f.file.is_literal(), f.file
1795         for_sig = f.file.for_signature()
1796         assert for_sig == 'baz.blat_file', for_sig
1797
1798         s = str(f.base)
1799         assert s == os.path.normpath('foo/bar/baz'), s
1800         assert f.base.is_literal(), f.base
1801         for_sig = f.base.for_signature()
1802         assert for_sig == 'baz.blat_base', for_sig
1803
1804         s = str(f.filebase)
1805         assert s == 'baz', s
1806         assert f.filebase.is_literal(), f.filebase
1807         for_sig = f.filebase.for_signature()
1808         assert for_sig == 'baz.blat_filebase', for_sig
1809
1810         s = str(f.suffix)
1811         assert s == '.blat', s
1812         assert f.suffix.is_literal(), f.suffix
1813         for_sig = f.suffix.for_signature()
1814         assert for_sig == 'baz.blat_suffix', for_sig
1815
1816         s = str(f.abspath)
1817         assert s == test.workpath('work', 'foo', 'bar', 'baz.blat'), s
1818         assert f.abspath.is_literal(), f.abspath
1819         for_sig = f.abspath.for_signature()
1820         assert for_sig == 'baz.blat_abspath', for_sig
1821
1822         s = str(f.posix)
1823         assert s == 'foo/bar/baz.blat', s
1824         assert f.posix.is_literal(), f.posix
1825         if f.posix != f:
1826             for_sig = f.posix.for_signature()
1827             assert for_sig == 'baz.blat_posix', for_sig
1828
1829         s = str(f.win32)
1830         assert s == 'foo\\bar\\baz.blat', repr(s)
1831         assert f.win32.is_literal(), f.win32
1832         if f.win32 != f:
1833             for_sig = f.win32.for_signature()
1834             assert for_sig == 'baz.blat_win32', for_sig
1835
1836         # And now, combinations!!!
1837         s = str(f.srcpath.base)
1838         assert s == os.path.normpath('foo/bar/baz'), s
1839         s = str(f.srcpath.dir)
1840         assert s == str(f.srcdir), s
1841         s = str(f.srcpath.posix)
1842         assert s == 'foo/bar/baz.blat', s
1843         s = str(f.srcpath.win32)
1844         assert s == 'foo\\bar\\baz.blat', s
1845
1846         # Test what happens with BuildDir()
1847         fs.BuildDir('foo', 'baz')
1848
1849         s = str(f.srcpath)
1850         assert s == os.path.normpath('baz/bar/baz.blat'), s
1851         assert f.srcpath.is_literal(), f.srcpath
1852         g = f.srcpath.get()
1853         assert isinstance(g, SCons.Node.FS.Entry), g.__class__
1854
1855         s = str(f.srcdir)
1856         assert s == os.path.normpath('baz/bar'), s
1857         assert f.srcdir.is_literal(), f.srcdir
1858         g = f.srcdir.get()
1859         assert isinstance(g, SCons.Node.FS.Dir), g.__class__
1860
1861         # And now what happens with BuildDir() + Repository()
1862         fs.Repository(test.workpath('repository'))
1863
1864         f = fs.Entry('foo/sub/file.suffix').get_subst_proxy()
1865         test.subdir('repository',
1866                     ['repository', 'baz'],
1867                     ['repository', 'baz', 'sub'])
1868
1869         rd = test.workpath('repository', 'baz', 'sub')
1870         rf = test.workpath('repository', 'baz', 'sub', 'file.suffix')
1871         test.write(rf, "\n")
1872
1873         s = str(f.srcpath)
1874         assert s == os.path.normpath('baz/sub/file.suffix'), s
1875         assert f.srcpath.is_literal(), f.srcpath
1876         g = f.srcpath.get()
1877         assert isinstance(g, SCons.Node.FS.Entry), g.__class__
1878
1879         s = str(f.srcdir)
1880         assert s == os.path.normpath('baz/sub'), s
1881         assert f.srcdir.is_literal(), f.srcdir
1882         g = f.srcdir.get()
1883         assert isinstance(g, SCons.Node.FS.Dir), g.__class__
1884
1885         s = str(f.rsrcpath)
1886         assert s == rf, s
1887         assert f.rsrcpath.is_literal(), f.rsrcpath
1888         g = f.rsrcpath.get()
1889         assert isinstance(g, SCons.Node.FS.File), g.__class__
1890
1891         s = str(f.rsrcdir)
1892         assert s == rd, s
1893         assert f.rsrcdir.is_literal(), f.rsrcdir
1894         g = f.rsrcdir.get()
1895         assert isinstance(g, SCons.Node.FS.Dir), g.__class__
1896
1897         # Check that attempts to access non-existent attributes of the
1898         # subst proxy generate the right exceptions and messages.
1899         caught = None
1900         try:
1901             fs.Dir('ddd').get_subst_proxy().no_such_attr
1902         except AttributeError, e:
1903             assert str(e) == "Dir instance 'ddd' has no attribute 'no_such_attr'", e
1904             caught = 1
1905         assert caught, "did not catch expected AttributeError"
1906
1907         caught = None
1908         try:
1909             fs.Entry('eee').get_subst_proxy().no_such_attr
1910         except AttributeError, e:
1911             assert str(e) == "Entry instance 'eee' has no attribute 'no_such_attr'", e
1912             caught = 1
1913         assert caught, "did not catch expected AttributeError"
1914
1915         caught = None
1916         try:
1917             fs.File('fff').get_subst_proxy().no_such_attr
1918         except AttributeError, e:
1919             assert str(e) == "File instance 'fff' has no attribute 'no_such_attr'", e
1920             caught = 1
1921         assert caught, "did not catch expected AttributeError"
1922
1923 class SaveStringsTestCase(unittest.TestCase):
1924     def runTest(self):
1925         """Test caching string values of nodes."""
1926         test=TestCmd(workdir='')
1927
1928         def setup(fs):
1929             fs.Dir('src')
1930             fs.Dir('d0')
1931             fs.Dir('d1')
1932
1933             d0_f = fs.File('d0/f')
1934             d1_f = fs.File('d1/f')
1935             d0_b = fs.File('d0/b')
1936             d1_b = fs.File('d1/b')
1937             d1_f.duplicate = 1
1938             d1_b.duplicate = 1
1939             d0_b.builder = 1
1940             d1_b.builder = 1
1941
1942             return [d0_f, d1_f, d0_b, d1_b]
1943
1944         def modify(nodes):
1945             d0_f, d1_f, d0_b, d1_b = nodes
1946             d1_f.duplicate = 0
1947             d1_b.duplicate = 0
1948             d0_b.builder = 0
1949             d1_b.builder = 0
1950
1951         fs1 = SCons.Node.FS.FS(test.workpath('fs1'))
1952         nodes = setup(fs1)
1953         fs1.BuildDir('d0', 'src', duplicate=0)
1954         fs1.BuildDir('d1', 'src', duplicate=1)
1955
1956         s = map(str, nodes)
1957         expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b'])
1958         assert s == expect, s
1959
1960         modify(nodes)
1961
1962         s = map(str, nodes)
1963         expect = map(os.path.normpath, ['src/f', 'src/f', 'd0/b', 'd1/b'])
1964         assert s == expect, s
1965
1966         SCons.Node.FS.save_strings(1)
1967         fs2 = SCons.Node.FS.FS(test.workpath('fs2'))
1968         nodes = setup(fs2)
1969         fs2.BuildDir('d0', 'src', duplicate=0)
1970         fs2.BuildDir('d1', 'src', duplicate=1)
1971
1972         s = map(str, nodes)
1973         expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b'])
1974         assert s == expect, s
1975
1976         modify(nodes)
1977
1978         s = map(str, nodes)
1979         expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b'])
1980         assert s == expect, s
1981
1982 if __name__ == "__main__":
1983     suite = unittest.TestSuite()
1984     suite.addTest(FSTestCase())
1985     suite.addTest(BuildDirTestCase())
1986     suite.addTest(EntryTestCase())
1987     suite.addTest(RepositoryTestCase())
1988     suite.addTest(find_fileTestCase())
1989     suite.addTest(StringDirTestCase())
1990     suite.addTest(stored_infoTestCase())
1991     suite.addTest(has_src_builderTestCase())
1992     suite.addTest(prepareTestCase())
1993     suite.addTest(get_actionsTestCase())
1994     suite.addTest(SConstruct_dirTestCase())
1995     suite.addTest(CacheDirTestCase())
1996     suite.addTest(clearTestCase())
1997     suite.addTest(postprocessTestCase())
1998     suite.addTest(SpecialAttrTestCase())
1999     suite.addTest(SaveStringsTestCase())
2000     if not unittest.TextTestRunner().run(suite).wasSuccessful():
2001         sys.exit(1)