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