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