Add more variable substitution tests.
[scons.git] / src / engine / SCons / UtilTests.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 types
31 import unittest
32 from SCons.Util import *
33 import TestCmd
34
35 class OutBuffer:
36     def __init__(self):
37         self.buffer = ""
38
39     def write(self, str):
40         self.buffer = self.buffer + str
41
42 class DummyNode:
43     """Simple node work-alike."""
44     def __init__(self, name):
45         self.name = os.path.normpath(name)
46     def __str__(self):
47         return self.name
48     def is_literal(self):
49         return 1
50     def rfile(self):
51         return self
52     def get_subst_proxy(self):
53         return self
54
55 class DummyEnv:
56     def __init__(self, dict={}):
57         self.dict = dict
58
59     def Dictionary(self, key = None):
60         if not key:
61             return self.dict
62         return self.dict[key]
63
64     def sig_dict(self):
65         dict = self.dict.copy()
66         dict["TARGETS"] = 'tsig'
67         dict["SOURCES"] = 'ssig'
68         return dict
69
70 def cs(target=None, source=None, env=None, for_signature=None):
71     return 'cs'
72
73 def cl(target=None, source=None, env=None, for_signature=None):
74     return ['cl']
75
76 def CmdGen1(target, source, env, for_signature):
77     # Nifty trick...since Environment references are interpolated,
78     # instantiate an instance of a callable class with this one,
79     # which will then get evaluated.
80     assert str(target) == 't', target
81     assert str(source) == 's', source
82     return "${CMDGEN2('foo', %d)}" % for_signature
83
84 class CmdGen2:
85     def __init__(self, mystr, forsig):
86         self.mystr = mystr
87         self.expect_for_signature = forsig
88
89     def __call__(self, target, source, env, for_signature):
90         assert str(target) == 't', target
91         assert str(source) == 's', source
92         assert for_signature == self.expect_for_signature, for_signature
93         return [ self.mystr, env.Dictionary('BAR') ]
94
95 if os.sep == '/':
96     def cvt(str):
97         return str
98 else:
99     def cvt(str):
100         return string.replace(str, '/', os.sep)
101
102 class UtilTestCase(unittest.TestCase):
103     def test_subst(self):
104         """Test the subst function"""
105         class MyNode(DummyNode):
106             """Simple node work-alike with some extra stuff for testing."""
107             def get_stuff(self, extra):
108                 return self.name + extra
109             foo = 1
110
111         class TestLiteral:
112             def __init__(self, literal):
113                 self.literal = literal
114             def __str__(self):
115                 return self.literal
116             def is_literal(self):
117                 return 1
118
119         def function_foo(arg):
120             pass
121
122         target = [ MyNode("./foo/bar.exe"),
123                    MyNode("/bar/baz.obj"),
124                    MyNode("../foo/baz.obj") ]
125         source = [ MyNode("./foo/blah.cpp"),
126                    MyNode("/bar/ack.cpp"),
127                    MyNode("../foo/ack.c") ]
128
129         loc = {
130             'xxx'       : None,
131             'null'      : '',
132             'zero'      : 0,
133             'one'       : 1,
134             'BAR'       : 'baz',
135             'ONE'       : '$TWO',
136             'TWO'       : '$THREE',
137             'THREE'     : 'four',
138
139             'AAA'       : 'a',
140             'BBB'       : 'b',
141             'CCC'       : 'c',
142
143             # $XXX$HHH should expand to GGGIII, not BADNEWS.
144             'XXX'       : '$FFF',
145             'FFF'       : 'GGG',
146             'HHH'       : 'III',
147             'FFFIII'    : 'BADNEWS',
148
149             'LITERAL'   : TestLiteral("$XXX"),
150
151             # Test that we can expand to and return a function.
152             #'FUNCTION'  : function_foo,
153
154             'CMDGEN1'   : CmdGen1,
155             'CMDGEN2'   : CmdGen2,
156
157             'NOTHING'   : "",
158             'NONE'      : None,
159
160             # Test various combinations of strings, lists and functions.
161             'N'         : None,
162             'X'         : 'x',
163             'Y'         : '$X',
164             'R'         : '$R',
165             'S'         : 'x y',
166             'LS'        : ['x y'],
167             'L'         : ['x', 'y'],
168             'CS'        : cs,
169             'CL'        : cl,
170
171             # Test function calls within ${}.
172             'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
173             'FUNC1'     : lambda x: x,
174             'FUNC2'     : lambda target, source, env, for_signature: ['x$CCC'],
175
176             # Test recursion.
177             #'RECURSE'   : 'foo $RECURSE bar',
178             #'RRR'       : 'foo $SSS bar',
179             #'SSS'       : '$RRR',
180         }
181
182         env = DummyEnv(loc)
183
184         # Basic tests of substitution functionality.
185         cases = [
186             # Basics:  strings without expansions are left alone, and
187             # the simplest possible expansion to a null-string value.
188             "test",                 "test",
189             "$null",                "",
190
191             # Test expansion of integer values.
192             "test $zero",           "test 0",
193             "test $one",            "test 1",
194
195             # Test multiple re-expansion of values.
196             "test $ONE",            "test four",
197
198             # Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions.
199             "test $TARGETS $SOURCES",
200             "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c",
201
202             "test ${TARGETS[:]} ${SOURCES[0]}",
203             "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp",
204
205             "test ${TARGETS[1:]}v",
206             "test /bar/baz.obj ../foo/baz.objv",
207
208             "test $TARGET",
209             "test foo/bar.exe",
210
211             "test $TARGET$FOO[0]",
212             "test foo/bar.exe[0]",
213
214             "test $TARGETS.foo",
215             "test 1 1 1",
216
217             "test ${SOURCES[0:2].foo}",
218             "test 1 1",
219
220             "test $SOURCE.foo",
221             "test 1",
222
223             "test ${TARGET.get_stuff('blah')}",
224             "test foo/bar.exeblah",
225
226             "test ${SOURCES.get_stuff('blah')}",
227             "test foo/blah.cppblah /bar/ack.cppblah ../foo/ack.cblah",
228
229             "test ${SOURCES[0:2].get_stuff('blah')}",
230             "test foo/blah.cppblah /bar/ack.cppblah",
231
232             "test ${SOURCES[0:2].get_stuff('blah')}",
233             "test foo/blah.cppblah /bar/ack.cppblah",
234
235             # Test that adjacent expansions don't get re-interpreted
236             # together.  The correct disambiguated expansion should be:
237             #   $XXX$HHH => ${FFF}III => GGGIII
238             # not:
239             #   $XXX$HHH => ${FFFIII} => BADNEWS
240             "$XXX$HHH",             "GGGIII",
241
242             # Test double-dollar-sign behavior.
243             "$$FFF$HHH",            "$FFFIII",
244
245             # Test that a Literal will stop dollar-sign substitution.
246             "$XXX $LITERAL $FFF",   "GGG $XXX GGG",
247
248             # Test that we don't blow up even if they subscript
249             # something in ways they "can't."
250             "${FFF[0]}",            "G",
251             "${FFF[7]}",            "",
252             "${NOTHING[1]}",        "",
253             "${NONE[2]}",           "",
254
255             # Test various combinations of strings and lists.
256             #None,                   '',
257             '',                     '',
258             'x',                    'x',
259             'x y',                  'x y',
260             '$N',                   '',
261             '$X',                   'x',
262             '$Y',                   'x',
263             #'$R',                   '',
264             '$S',                   'x y',
265             '$LS',                  'x y',
266             '$L',                   'x y',
267             #cs,                     'cs',
268             #cl,                     'cl',
269             '$CS',                  'cs',
270             '$CL',                  'cl',
271
272             # Test function calls within ${}.
273             '$FUNCCALL',            'a xc b',
274         ]
275
276         kwargs = {'target' : target, 'source' : source}
277
278         failed = 0
279         while cases:
280             input, expect = cases[:2]
281             expect = cvt(expect)
282             #print "    " + input
283             result = apply(scons_subst, (input, env), kwargs)
284             if result != expect:
285                 if failed == 0: print
286                 print "    input %s => %s did not match %s" % (repr(input), repr(result), repr(expect))
287                 failed = failed + 1
288             del cases[:2]
289         assert failed == 0, "%d subst() cases failed" % failed
290
291         # Tests of the various SUBST_* modes of substitution.
292         subst_cases = [
293             "test $xxx",
294                 "test ",
295                 "test",
296                 "test",
297
298             "test $($xxx$)",
299                 "test $($)",
300                 "test",
301                 "test",
302
303             "test $( $xxx $)",
304                 "test $(  $)",
305                 "test",
306                 "test",
307
308             "$AAA ${AAA}A $BBBB $BBB",
309                 "a aA  b",
310                 "a aA b",
311                 "a aA b",
312
313             #"$RECURSE",
314             #   "foo  bar",
315             #   "foo bar",
316             #   "foo bar",
317
318             #"$RRR",
319             #   "foo  bar",
320             #   "foo bar",
321             #   "foo bar",
322         ]
323
324         failed = 0
325         while subst_cases:
326             input, eraw, ecmd, esig = subst_cases[:4]
327             result = scons_subst(input, env, mode=SUBST_RAW)
328             if result != eraw:
329                 if failed == 0: print
330                 print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
331                 failed = failed + 1
332             result = scons_subst(input, env, mode=SUBST_CMD)
333             if result != ecmd:
334                 if failed == 0: print
335                 print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
336                 failed = failed + 1
337             result = scons_subst(input, env, mode=SUBST_SIG)
338             if result != esig:
339                 if failed == 0: print
340                 print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
341                 failed = failed + 1
342             del subst_cases[:4]
343         assert failed == 0, "%d subst() mode cases failed" % failed
344
345         # Test interpolating a callable.
346         newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS",
347                              env, target=MyNode('t'), source=MyNode('s'))
348         assert newcom == "test foo baz s t", newcom
349
350         # Test returning a function.
351         #env = DummyEnv({'FUNCTION' : foo})
352         #func = scons_subst("$FUNCTION", env, mode=SUBST_RAW, call=None)
353         #assert func is function_foo, func
354         #func = scons_subst("$FUNCTION", env, mode=SUBST_CMD, call=None)
355         #assert func is function_foo, func
356         #func = scons_subst("$FUNCTION", env, mode=SUBST_SIG, call=None)
357         #assert func is function_foo, func
358
359     def test_subst_list(self):
360         """Testing the scons_subst_list() method..."""
361         target = [ DummyNode("./foo/bar.exe"),
362                    DummyNode("/bar/baz with spaces.obj"),
363                    DummyNode("../foo/baz.obj") ]
364         source = [ DummyNode("./foo/blah with spaces.cpp"),
365                    DummyNode("/bar/ack.cpp"),
366                    DummyNode("../foo/ack.c") ]
367
368         loc = {
369             'xxx'       : None,
370             'NEWLINE'   : 'before\nafter',
371
372             'AAA'       : 'a',
373             'BBB'       : 'b',
374             'CCC'       : 'c',
375
376             'DO'        : DummyNode('do something'),
377             'FOO'       : DummyNode('foo.in'),
378             'BAR'       : DummyNode('bar with spaces.out'),
379             'CRAZY'     : DummyNode('crazy\nfile.in'),
380
381             # $XXX$HHH should expand to GGGIII, not BADNEWS.
382             'XXX'       : '$FFF',
383             'FFF'       : 'GGG',
384             'HHH'       : 'III',
385             'FFFIII'    : 'BADNEWS',
386
387             'CMDGEN1'   : CmdGen1,
388             'CMDGEN2'   : CmdGen2,
389
390             'LITERALS'  : [ Literal('foo\nwith\nnewlines'),
391                             Literal('bar\nwith\nnewlines') ],
392
393             # Test various combinations of strings, lists and functions.
394             'N'         : None,
395             'X'         : 'x',
396             'Y'         : '$X',
397             'R'         : '$R',
398             'S'         : 'x y',
399             'LS'        : ['x y'],
400             'L'         : ['x', 'y'],
401             'CS'        : cs,
402             'CL'        : cl,
403
404             # Test function calls within ${}.
405             'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
406             'FUNC1'     : lambda x: x,
407             'FUNC2'     : lambda target, source, env, for_signature: ['x$CCC'],
408         }
409
410         env = DummyEnv(loc)
411
412         cases = [
413             "$TARGETS",
414             [
415                 ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
416             ],
417
418             "$SOURCES $NEWLINE $TARGETS",
419             [
420                 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
421                 ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
422             ],
423
424             "$SOURCES$NEWLINE",
425             [
426                 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
427                 ["after"],
428             ],
429
430             "$DO --in=$FOO --out=$BAR",
431             [
432                 ["do something", "--in=foo.in", "--out=bar with spaces.out"],
433             ],
434
435             # This test is now fixed, and works like it should.
436             "$DO --in=$CRAZY --out=$BAR",
437             [
438                 ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
439             ],
440
441             # Try passing a list to scons_subst_list().
442             [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
443             [
444                 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
445                 ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
446             ],
447
448             # Test against a former bug in scons_subst_list().
449             "$XXX$HHH",
450             [
451                 ["GGGIII"],
452             ],
453
454             # Test double-dollar-sign behavior.
455             "$$FFF$HHH",
456             [
457                 ["$FFFIII"],
458             ],
459
460             # Test various combinations of strings, lists and functions.
461             None,                   [[]],
462             #[None],                 [[]],
463             '',                     [[]],
464             [''],                   [[]],
465             'x',                    [['x']],
466             ['x'],                  [['x']],
467             'x y',                  [['x', 'y']],
468             ['x y'],                [['x y']],
469             ['x', 'y'],             [['x', 'y']],
470             '$N',                   [[]],
471             ['$N'],                 [[]],
472             '$X',                   [['x']],
473             ['$X'],                 [['x']],
474             '$Y',                   [['x']],
475             ['$Y'],                 [['x']],
476             #'$R',                   [[]],
477             #['$R'],                 [[]],
478             '$S',                   [['x', 'y']],
479             ['$S'],                 [['x', 'y']],
480             '$LS',                  [['x y']],
481             ['$LS'],                [['x y']],
482             '$L',                   [['x', 'y']],
483             ['$L'],                 [['x', 'y']],
484             #cs,                     [['cs']],
485             #[cs],                   [['cs']],
486             #cl,                     [['cl']],
487             #[cl],                   [['cl']],
488             '$CS',                  [['cs']],
489             ['$CS'],                [['cs']],
490             '$CL',                  [['cl']],
491             ['$CL'],                [['cl']],
492
493             # Test 
494             '$FUNCCALL',            [['a', 'xc', 'b']],
495         ]
496
497         kwargs = {'target' : target, 'source' : source}
498
499         failed = 0
500         while cases:
501             input, expect = cases[:2]
502             expect = map(lambda l: map(cvt, l), expect)
503             result = apply(scons_subst_list, (input, env), kwargs)
504             if result != expect:
505                 if failed == 0: print
506                 print "    input %s => %s did not match %s" % (repr(input), result, repr(expect))
507                 failed = failed + 1
508             del cases[:2]
509         assert failed == 0, "%d subst_list() cases failed" % failed
510
511         # Test interpolating a callable.
512         _t = DummyNode('t')
513         _s = DummyNode('s')
514         cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES",
515                                     env, target=_t, source=_s)
516         assert cmd_list == [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list
517
518         # Test escape functionality.
519         def escape_func(foo):
520             return '**' + foo + '**'
521         cmd_list = scons_subst_list("abc $LITERALS xyz", env)
522         assert cmd_list == [['abc',
523                              'foo\nwith\nnewlines',
524                              'bar\nwith\nnewlines',
525                              'xyz']], cmd_list
526         cmd_list[0][0].escape(escape_func)
527         assert cmd_list[0][0] == 'abc', c
528         cmd_list[0][1].escape(escape_func)
529         assert cmd_list[0][1] == '**foo\nwith\nnewlines**', c
530         cmd_list[0][2].escape(escape_func)
531         assert cmd_list[0][2] == '**bar\nwith\nnewlines**', c
532         cmd_list[0][3].escape(escape_func)
533         assert cmd_list[0][3] == 'xyz', c
534
535     def test_splitext(self):
536         assert splitext('foo') == ('foo','')
537         assert splitext('foo.bar') == ('foo','.bar')
538         assert splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'')
539
540     def test_quote_spaces(self):
541         """Testing the quote_spaces() method..."""
542         q = quote_spaces('x')
543         assert q == 'x', q
544
545         q = quote_spaces('x x')
546         assert q == '"x x"', q
547
548         q = quote_spaces('x\tx')
549         assert q == '"x\tx"', q
550
551     def test_render_tree(self):
552         class Node:
553             def __init__(self, name, children=[]):
554                 self.children = children
555                 self.name = name
556             def __str__(self):
557                 return self.name
558
559         def get_children(node):
560             return node.children
561
562         windows_h = Node("windows.h")
563         stdlib_h = Node("stdlib.h")
564         stdio_h = Node("stdio.h")
565         bar_c = Node("bar.c", [stdlib_h, windows_h])
566         bar_o = Node("bar.o", [bar_c])
567         foo_c = Node("foo.c", [stdio_h])
568         foo_o = Node("foo.o", [foo_c])
569         foo = Node("foo", [foo_o, bar_o])
570
571         expect = """\
572 +-foo
573   +-foo.o
574   | +-foo.c
575   |   +-stdio.h
576   +-bar.o
577     +-bar.c
578       +-stdlib.h
579       +-windows.h
580 """
581
582         actual = render_tree(foo, get_children)
583         assert expect == actual, (expect, actual)
584
585         bar_h = Node('bar.h', [stdlib_h])
586         blat_h = Node('blat.h', [stdlib_h])
587         blat_c = Node('blat.c', [blat_h, bar_h])
588         blat_o = Node('blat.o', [blat_c])
589
590         expect = """\
591 +-blat.o
592   +-blat.c
593     +-blat.h
594     | +-stdlib.h
595     +-bar.h
596 """
597
598         actual = render_tree(blat_o, get_children, 1)
599         assert expect == actual, (expect, actual)
600
601     def test_is_Dict(self):
602         assert is_Dict({})
603         import UserDict
604         assert is_Dict(UserDict.UserDict())
605         assert not is_Dict([])
606         assert not is_Dict("")
607         if hasattr(types, 'UnicodeType'):
608             exec "assert not is_Dict(u'')"
609
610     def test_is_List(self):
611         assert is_List([])
612         import UserList
613         assert is_List(UserList.UserList())
614         assert not is_List({})
615         assert not is_List("")
616         if hasattr(types, 'UnicodeType'):
617             exec "assert not is_List(u'')"
618
619     def test_is_String(self):
620         assert is_String("")
621         if hasattr(types, 'UnicodeType'):
622             exec "assert is_String(u'')"
623         try:
624             import UserString
625         except:
626             pass
627         else:
628             assert is_String(UserString.UserString(''))
629         assert not is_String({})
630         assert not is_String([])
631
632     def test_to_String(self):
633         """Test the to_String() method."""
634         assert to_String(1) == "1", to_String(1)
635         assert to_String([ 1, 2, 3]) == str([1, 2, 3]), to_String([1,2,3])
636         assert to_String("foo") == "foo", to_String("foo")
637
638         try:
639             import UserString
640
641             s1=UserString.UserString('blah')
642             assert to_String(s1) == s1, s1
643             assert to_String(s1) == 'blah', s1
644
645             class Derived(UserString.UserString):
646                 pass
647             s2 = Derived('foo')
648             assert to_String(s2) == s2, s2
649             assert to_String(s2) == 'foo', s2
650
651             if hasattr(types, 'UnicodeType'):
652                 s3=UserString.UserString(unicode('bar'))
653                 assert to_String(s3) == s3, s3
654                 assert to_String(s3) == unicode('bar'), s3
655                 assert type(to_String(s3)) is types.UnicodeType, \
656                        type(to_String(s3))
657         except ImportError:
658             pass
659
660         if hasattr(types, 'UnicodeType'):
661             s4 = unicode('baz')
662             assert to_String(s4) == unicode('baz'), to_String(s4)
663             assert type(to_String(s4)) is types.UnicodeType, \
664                    type(to_String(s4))
665
666     def test_WhereIs(self):
667         test = TestCmd.TestCmd(workdir = '')
668
669         sub1_xxx_exe = test.workpath('sub1', 'xxx.exe')
670         sub2_xxx_exe = test.workpath('sub2', 'xxx.exe')
671         sub3_xxx_exe = test.workpath('sub3', 'xxx.exe')
672         sub4_xxx_exe = test.workpath('sub4', 'xxx.exe')
673
674         test.subdir('subdir', 'sub1', 'sub2', 'sub3', 'sub4')
675
676         if sys.platform != 'win32':
677             test.write(sub1_xxx_exe, "\n")
678
679         os.mkdir(sub2_xxx_exe)
680
681         test.write(sub3_xxx_exe, "\n")
682         os.chmod(sub3_xxx_exe, 0777)
683
684         test.write(sub4_xxx_exe, "\n")
685         os.chmod(sub4_xxx_exe, 0777)
686
687         env_path = os.environ['PATH']
688
689         pathdirs_1234 = [ test.workpath('sub1'),
690                           test.workpath('sub2'),
691                           test.workpath('sub3'),
692                           test.workpath('sub4'),
693                         ] + string.split(env_path, os.pathsep)
694
695         pathdirs_1243 = [ test.workpath('sub1'),
696                           test.workpath('sub2'),
697                           test.workpath('sub4'),
698                           test.workpath('sub3'),
699                         ] + string.split(env_path, os.pathsep)
700
701         os.environ['PATH'] = string.join(pathdirs_1234, os.pathsep)
702         wi = WhereIs('xxx.exe')
703         assert wi == test.workpath(sub3_xxx_exe), wi
704         wi = WhereIs('xxx.exe', pathdirs_1243)
705         assert wi == test.workpath(sub4_xxx_exe), wi
706         wi = WhereIs('xxx.exe', string.join(pathdirs_1243, os.pathsep))
707         assert wi == test.workpath(sub4_xxx_exe), wi
708
709         os.environ['PATH'] = string.join(pathdirs_1243, os.pathsep)
710         wi = WhereIs('xxx.exe')
711         assert wi == test.workpath(sub4_xxx_exe), wi
712         wi = WhereIs('xxx.exe', pathdirs_1234)
713         assert wi == test.workpath(sub3_xxx_exe), wi
714         wi = WhereIs('xxx.exe', string.join(pathdirs_1234, os.pathsep))
715         assert wi == test.workpath(sub3_xxx_exe), wi
716
717         if sys.platform == 'win32':
718             wi = WhereIs('xxx', pathext = '')
719             assert wi is None, wi
720
721             wi = WhereIs('xxx', pathext = '.exe')
722             assert wi == test.workpath(sub4_xxx_exe), wi
723
724             wi = WhereIs('xxx', path = pathdirs_1234, pathext = '.BAT;.EXE')
725             assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi
726
727             # Test that we return a normalized path even when
728             # the path contains forward slashes.
729             forward_slash = test.workpath('') + '/sub3'
730             wi = WhereIs('xxx', path = forward_slash, pathext = '.EXE')
731             assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi
732
733     def test_is_valid_construction_var(self):
734         """Testing is_valid_construction_var()"""
735         r = is_valid_construction_var("_a")
736         assert not r is None, r
737         r = is_valid_construction_var("z_")
738         assert not r is None, r
739         r = is_valid_construction_var("X_")
740         assert not r is None, r
741         r = is_valid_construction_var("2a")
742         assert r is None, r
743         r = is_valid_construction_var("a2_")
744         assert not r is None, r
745         r = is_valid_construction_var("/")
746         assert r is None, r
747         r = is_valid_construction_var("_/")
748         assert r is None, r
749         r = is_valid_construction_var("a/")
750         assert r is None, r
751         r = is_valid_construction_var(".b")
752         assert r is None, r
753         r = is_valid_construction_var("_.b")
754         assert r is None, r
755         r = is_valid_construction_var("b1._")
756         assert r is None, r
757         r = is_valid_construction_var("-b")
758         assert r is None, r
759         r = is_valid_construction_var("_-b")
760         assert r is None, r
761         r = is_valid_construction_var("b1-_")
762         assert r is None, r
763
764     def test_get_env_var(self):
765         """Testing get_environment_var()."""
766         assert get_environment_var("$FOO") == "FOO", get_environment_var("$FOO")
767         assert get_environment_var("${BAR}") == "BAR", get_environment_var("${BAR}")
768         assert get_environment_var("$FOO_BAR1234") == "FOO_BAR1234", get_environment_var("$FOO_BAR1234")
769         assert get_environment_var("${BAR_FOO1234}") == "BAR_FOO1234", get_environment_var("${BAR_FOO1234}")
770         assert get_environment_var("${BAR}FOO") == None, get_environment_var("${BAR}FOO")
771         assert get_environment_var("$BAR ") == None, get_environment_var("$BAR ")
772         assert get_environment_var("FOO$BAR") == None, get_environment_var("FOO$BAR")
773         assert get_environment_var("$FOO[0]") == None, get_environment_var("$FOO[0]")
774         assert get_environment_var("${some('complex expression')}") == None, get_environment_var("${some('complex expression')}")
775
776     def test_Proxy(self):
777         """Test generic Proxy class."""
778         class Subject:
779             def foo(self):
780                 return 1
781             def bar(self):
782                 return 2
783
784         s=Subject()
785         s.baz = 3
786
787         class ProxyTest(Proxy):
788             def bar(self):
789                 return 4
790
791         p=ProxyTest(s)
792
793         assert p.foo() == 1, p.foo()
794         assert p.bar() == 4, p.bar()
795         assert p.baz == 3, p.baz
796
797         p.baz = 5
798         s.baz = 6
799
800         assert p.baz == 5, p.baz
801         assert p.get() == s, p.get()
802
803     def test_Literal(self):
804         """Test the Literal() function."""
805         input_list = [ '$FOO', Literal('$BAR') ]
806         dummy_env = DummyEnv({ 'FOO' : 'BAZ', 'BAR' : 'BLAT' })
807
808         def escape_func(cmd):
809             return '**' + cmd + '**'
810
811         cmd_list = scons_subst_list(input_list, dummy_env)
812         map(lambda x, e=escape_func: x.escape(e), cmd_list[0])
813         cmd_list = map(str, cmd_list[0])
814         assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
815
816     def test_SpecialAttrWrapper(self):
817         """Test the SpecialAttrWrapper() function."""
818         input_list = [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
819         dummy_env = DummyEnv({ 'FOO' : 'BAZ', 'BAR' : 'BLAT' })
820
821         def escape_func(cmd):
822             return '**' + cmd + '**'
823
824         cmd_list = scons_subst_list(input_list, dummy_env)
825         map(lambda x, e=escape_func: x.escape(e), cmd_list[0])
826         cmd_list = map(str, cmd_list[0])
827         assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
828
829         cmd_list = scons_subst_list(input_list, dummy_env, mode=SUBST_SIG)
830         map(lambda x, e=escape_func: x.escape(e), cmd_list[0])
831         cmd_list = map(str, cmd_list[0])
832         assert cmd_list == ['BAZ', '**BLEH**'], cmd_list
833
834     def test_mapPaths(self):
835         """Test the mapPaths function"""
836         class MyFileNode:
837             def __init__(self, path):
838                 self.path = path
839             def __str__(self):
840                 return self.path
841
842         dir=MyFileNode('foo')
843         file=MyFileNode('bar/file')
844
845         class DummyEnv:
846             def subst(self, arg):
847                 return 'bar'
848
849         res = mapPaths([ file, 'baz', 'blat/boo', '#test' ], dir)
850         assert res[0] == file, res[0]
851         assert res[1] == os.path.join('foo', 'baz'), res[1]
852         assert res[2] == os.path.join('foo', 'blat/boo'), res[2]
853         assert res[3] == '#test', res[3]
854
855         env=DummyEnv()
856         res=mapPaths('bleh', dir, env)
857         assert res[0] == os.path.normpath('foo/bar'), res[1]
858
859     def test_display(self):
860         old_stdout = sys.stdout
861         sys.stdout = OutBuffer()
862         display("line1")
863         display.set_mode(0)
864         display("line2")
865         display.set_mode(1)
866         display("line3")
867
868         assert sys.stdout.buffer == "line1\nline3\n"
869         sys.stdout = old_stdout
870
871     def test_fs_delete(self):
872         test = TestCmd.TestCmd(workdir = '')
873         base = test.workpath('')
874         xxx = test.workpath('xxx.xxx')
875         ZZZ = test.workpath('ZZZ.ZZZ')
876         sub1_yyy = test.workpath('sub1', 'yyy.yyy')
877
878         test.subdir('sub1')
879         test.write(xxx, "\n")
880         test.write(ZZZ, "\n")
881         test.write(sub1_yyy, "\n")
882
883         old_stdout = sys.stdout
884         sys.stdout = OutBuffer()
885
886         exp = "Removed " + os.path.join(base, ZZZ) + "\n" + \
887               "Removed " + os.path.join(base, sub1_yyy) + '\n' + \
888               "Removed directory " + os.path.join(base, 'sub1') + '\n' + \
889               "Removed " + os.path.join(base, xxx) + '\n' + \
890               "Removed directory " + base + '\n'
891
892         fs_delete(base, remove=0)
893         assert sys.stdout.buffer == exp, sys.stdout.buffer
894         assert os.path.exists(sub1_yyy)
895
896         sys.stdout.buffer = ""
897         fs_delete(base, remove=1)
898         assert sys.stdout.buffer == exp
899         assert not os.path.exists(base)
900
901         test._dirlist = None
902         sys.stdout = old_stdout
903
904     def test_get_native_path(self):
905         """Test the get_native_path() function."""
906         import tempfile
907         filename = tempfile.mktemp()
908         str = '1234567890 ' + filename
909         open(filename, 'w').write(str)
910         assert open(get_native_path(filename)).read() == str
911
912     def test_subst_dict(self):
913         """Test substituting dictionary values in an Action
914         """
915         env = DummyEnv({'a' : 'A', 'b' : 'B'})
916         d = subst_dict([], [], env)
917         assert d['__env__'] is env, d['__env__']
918
919         d = subst_dict(target = DummyNode('t'), source = DummyNode('s'), env=DummyEnv())
920         assert str(d['TARGETS'][0]) == 't', d['TARGETS']
921         assert str(d['TARGET']) == 't', d['TARGET']
922         assert str(d['SOURCES'][0]) == 's', d['SOURCES']
923         assert str(d['SOURCE']) == 's', d['SOURCE']
924
925         d = subst_dict(target = [DummyNode('t1'), DummyNode('t2')],
926                        source = [DummyNode('s1'), DummyNode('s2')],
927                        env = DummyEnv())
928         TARGETS = map(lambda x: str(x), d['TARGETS'])
929         TARGETS.sort()
930         assert TARGETS == ['t1', 't2'], d['TARGETS']
931         assert str(d['TARGET']) == 't1', d['TARGET']
932         SOURCES = map(lambda x: str(x), d['SOURCES'])
933         SOURCES.sort()
934         assert SOURCES == ['s1', 's2'], d['SOURCES']
935         assert str(d['SOURCE']) == 's1', d['SOURCE']
936
937         class N:
938             def __init__(self, name):
939                 self.name = name
940             def __str__(self):
941                 return self.name
942             def rfile(self):
943                 return self.__class__('rstr-' + self.name)
944             def get_subst_proxy(self):
945                 return self
946
947         d = subst_dict(target = [N('t3'), DummyNode('t4')],
948                        source = [DummyNode('s3'), N('s4')],
949                        env = DummyEnv())
950         TARGETS = map(lambda x: str(x), d['TARGETS'])
951         TARGETS.sort()
952         assert TARGETS == ['t3', 't4'], d['TARGETS']
953         SOURCES = map(lambda x: str(x), d['SOURCES'])
954         SOURCES.sort()
955         assert SOURCES == ['rstr-s4', 's3'], d['SOURCES']
956
957     def test_PrependPath(self):
958         """Test prepending to a path"""
959         p1 = r'C:\dir\num\one;C:\dir\num\two'
960         p2 = r'C:\mydir\num\one;C:\mydir\num\two'
961         # have to include the pathsep here so that the test will work on UNIX too.
962         p1 = PrependPath(p1,r'C:\dir\num\two',sep = ';')
963         p1 = PrependPath(p1,r'C:\dir\num\three',sep = ';')
964         p2 = PrependPath(p2,r'C:\mydir\num\three',sep = ';')
965         p2 = PrependPath(p2,r'C:\mydir\num\one',sep = ';')
966         assert(p1 == r'C:\dir\num\three;C:\dir\num\two;C:\dir\num\one')
967         assert(p2 == r'C:\mydir\num\one;C:\mydir\num\three;C:\mydir\num\two')
968
969     def test_AppendPath(self):
970         """Test appending to a path."""
971         p1 = r'C:\dir\num\one;C:\dir\num\two'
972         p2 = r'C:\mydir\num\one;C:\mydir\num\two'
973         # have to include the pathsep here so that the test will work on UNIX too.
974         p1 = AppendPath(p1,r'C:\dir\num\two',sep = ';')
975         p1 = AppendPath(p1,r'C:\dir\num\three',sep = ';')
976         p2 = AppendPath(p2,r'C:\mydir\num\three',sep = ';')
977         p2 = AppendPath(p2,r'C:\mydir\num\one',sep = ';')
978         assert(p1 == r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three')
979         assert(p2 == r'C:\mydir\num\two;C:\mydir\num\three;C:\mydir\num\one')
980
981     def test_NodeList(self):
982         """Test NodeList class"""
983         class TestClass:
984             def __init__(self, name, child=None):
985                 self.child = child
986                 self.bar = name
987             def foo(self):
988                 return self.bar + "foo"
989             def getself(self):
990                 return self
991
992         t1 = TestClass('t1', TestClass('t1child'))
993         t2 = TestClass('t2', TestClass('t2child'))
994         t3 = TestClass('t3')
995
996         nl = NodeList([t1, t2, t3])
997         assert nl.foo() == [ 't1foo', 't2foo', 't3foo' ], nl.foo()
998         assert nl.bar == [ 't1', 't2', 't3' ], nl.bar
999         assert nl.getself().bar == [ 't1', 't2', 't3' ], nl.getself().bar
1000         assert nl[0:2].child.foo() == [ 't1childfoo', 't2childfoo' ], \
1001                nl[0:2].child.foo()
1002         assert nl[0:2].child.bar == [ 't1child', 't2child' ], \
1003                nl[0:2].child.bar
1004
1005     def test_Selector(self):
1006         """Test the Selector class"""
1007
1008         s = Selector({'a' : 'AAA', 'b' : 'BBB'})
1009         assert s['a'] == 'AAA', s['a']
1010         assert s['b'] == 'BBB', s['b']
1011         exc_caught = None
1012         try:
1013             x = s['c']
1014         except KeyError:
1015             exc_caught = 1
1016         assert exc_caught, "should have caught a KeyError"
1017         s['c'] = 'CCC'
1018         assert s['c'] == 'CCC', s['c']
1019
1020         class DummyEnv(UserDict.UserDict):
1021             def subst(self, key):
1022                 if key[0] == '$':
1023                     return self[key[1:]]
1024                 return key
1025
1026         env = DummyEnv()
1027
1028         s = Selector({'.d' : 'DDD', '.e' : 'EEE'})
1029         ret = s(env, ['foo.d'])
1030         assert ret == 'DDD', ret
1031         ret = s(env, ['bar.e'])
1032         assert ret == 'EEE', ret
1033         ret = s(env, ['bar.x'])
1034         assert ret == None, ret
1035         s[None] = 'XXX'
1036         ret = s(env, ['bar.x'])
1037         assert ret == 'XXX', ret
1038
1039         env = DummyEnv({'FSUFF' : '.f', 'GSUFF' : '.g'})
1040
1041         s = Selector({'$FSUFF' : 'FFF', '$GSUFF' : 'GGG'})
1042         ret = s(env, ['foo.f'])
1043         assert ret == 'FFF', ret
1044         ret = s(env, ['bar.g'])
1045         assert ret == 'GGG', ret
1046
1047 if __name__ == "__main__":
1048     suite = unittest.makeSuite(UtilTestCase, 'test_')
1049     if not unittest.TextTestRunner().run(suite).wasSuccessful():
1050         sys.exit(1)