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