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