Merged revisions 2121-2135 via svnmerge from
[scons.git] / src / engine / SCons / SubstTests.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 StringIO
30 import sys
31 import types
32 import unittest
33
34 from UserDict import UserDict
35
36 import SCons.Errors
37
38 from SCons.Subst import *
39
40 class DummyNode:
41     """Simple node work-alike."""
42     def __init__(self, name):
43         self.name = os.path.normpath(name)
44     def __str__(self):
45         return self.name
46     def is_literal(self):
47         return 1
48     def rfile(self):
49         return self
50     def get_subst_proxy(self):
51         return self
52
53 class DummyEnv:
54     def __init__(self, dict={}):
55         self.dict = dict
56
57     def Dictionary(self, key = None):
58         if not key:
59             return self.dict
60         return self.dict[key]
61
62     def __getitem__(self, key):
63         return self.dict[key]
64
65     def get(self, key, default):
66         return self.dict.get(key, default)
67
68     def sig_dict(self):
69         dict = self.dict.copy()
70         dict["TARGETS"] = 'tsig'
71         dict["SOURCES"] = 'ssig'
72         return dict
73
74 def cs(target=None, source=None, env=None, for_signature=None):
75     return 'cs'
76
77 def cl(target=None, source=None, env=None, for_signature=None):
78     return ['cl']
79
80 def CmdGen1(target, source, env, for_signature):
81     # Nifty trick...since Environment references are interpolated,
82     # instantiate an instance of a callable class with this one,
83     # which will then get evaluated.
84     assert str(target) == 't', target
85     assert str(source) == 's', source
86     return "${CMDGEN2('foo', %d)}" % for_signature
87
88 class CmdGen2:
89     def __init__(self, mystr, forsig):
90         self.mystr = mystr
91         self.expect_for_signature = forsig
92
93     def __call__(self, target, source, env, for_signature):
94         assert str(target) == 't', target
95         assert str(source) == 's', source
96         assert for_signature == self.expect_for_signature, for_signature
97         return [ self.mystr, env.Dictionary('BAR') ]
98
99 if os.sep == '/':
100     def cvt(str):
101         return str
102 else:
103     def cvt(str):
104         return string.replace(str, '/', os.sep)
105
106 class SubstTestCase(unittest.TestCase):
107     def test_subst(self):
108         """Test the subst() function"""
109         class MyNode(DummyNode):
110             """Simple node work-alike with some extra stuff for testing."""
111             def __init__(self, name):
112                 DummyNode.__init__(self, name)
113                 class Attribute:
114                     pass
115                 self.attribute = Attribute()
116                 self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
117                 self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
118             def get_stuff(self, extra):
119                 return self.name + extra
120             foo = 1
121
122         class TestLiteral:
123             def __init__(self, literal):
124                 self.literal = literal
125             def __str__(self):
126                 return self.literal
127             def is_literal(self):
128                 return 1
129
130         class TestCallable:
131             def __init__(self, value):
132                 self.value = value
133             def __call__(self):
134                 pass
135             def __str__(self):
136                 return self.value
137
138         def function_foo(arg):
139             pass
140
141         target = [ MyNode("./foo/bar.exe"),
142                    MyNode("/bar/baz.obj"),
143                    MyNode("../foo/baz.obj") ]
144         source = [ MyNode("./foo/blah.cpp"),
145                    MyNode("/bar/ack.cpp"),
146                    MyNode("../foo/ack.c") ]
147
148         callable_object = TestCallable('callable-1')
149
150         loc = {
151             'xxx'       : None,
152             'null'      : '',
153             'zero'      : 0,
154             'one'       : 1,
155             'BAR'       : 'baz',
156             'ONE'       : '$TWO',
157             'TWO'       : '$THREE',
158             'THREE'     : 'four',
159
160             'AAA'       : 'a',
161             'BBB'       : 'b',
162             'CCC'       : 'c',
163
164             # $XXX$HHH should expand to GGGIII, not BADNEWS.
165             'XXX'       : '$FFF',
166             'FFF'       : 'GGG',
167             'HHH'       : 'III',
168             'FFFIII'    : 'BADNEWS',
169
170             'LITERAL'   : TestLiteral("$XXX"),
171
172             # Test that we can expand to and return a function.
173             #'FUNCTION'  : function_foo,
174
175             'CMDGEN1'   : CmdGen1,
176             'CMDGEN2'   : CmdGen2,
177
178             'NOTHING'   : "",
179             'NONE'      : None,
180
181             # Test various combinations of strings, lists and functions.
182             'N'         : None,
183             'X'         : 'x',
184             'Y'         : '$X',
185             'R'         : '$R',
186             'S'         : 'x y',
187             'LS'        : ['x y'],
188             'L'         : ['x', 'y'],
189             'TS'        : ('x y'),
190             'T'         : ('x', 'y'),
191             'CS'        : cs,
192             'CL'        : cl,
193
194             # Test function calls within ${}.
195             'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
196             'FUNC1'     : lambda x: x,
197             'FUNC2'     : lambda target, source, env, for_signature: ['x$CCC'],
198
199             # Various tests refactored from ActionTests.py.
200             'LIST'      : [["This", "is", "$(", "$a", "$)", "test"]],
201
202             # Test recursion.
203             'RECURSE'   : 'foo $RECURSE bar',
204             'RRR'       : 'foo $SSS bar',
205             'SSS'       : '$RRR',
206
207             # Test callables that don't match the calling arguments.
208             'CALLABLE'  : callable_object,
209         }
210
211         env = DummyEnv(loc)
212
213         # Basic tests of substitution functionality.
214         cases = [
215             # Basics:  strings without expansions are left alone, and
216             # the simplest possible expansion to a null-string value.
217             "test",                 "test",
218             "$null",                "",
219
220             # Test expansion of integer values.
221             "test $zero",           "test 0",
222             "test $one",            "test 1",
223
224             # Test multiple re-expansion of values.
225             "test $ONE",            "test four",
226
227             # Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions.
228             "test $TARGETS $SOURCES",
229             "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c",
230
231             "test ${TARGETS[:]} ${SOURCES[0]}",
232             "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp",
233
234             "test ${TARGETS[1:]}v",
235             "test /bar/baz.obj ../foo/baz.objv",
236
237             "test $TARGET",
238             "test foo/bar.exe",
239
240             "test $TARGET$FOO[0]",
241             "test foo/bar.exe[0]",
242
243             "test $TARGETS.foo",
244             "test 1 1 1",
245
246             "test ${SOURCES[0:2].foo}",
247             "test 1 1",
248
249             "test $SOURCE.foo",
250             "test 1",
251
252             "test ${TARGET.get_stuff('blah')}",
253             "test foo/bar.exeblah",
254
255             "test ${SOURCES.get_stuff('blah')}",
256             "test foo/blah.cppblah /bar/ack.cppblah ../foo/ack.cblah",
257
258             "test ${SOURCES[0:2].get_stuff('blah')}",
259             "test foo/blah.cppblah /bar/ack.cppblah",
260
261             "test ${SOURCES[0:2].get_stuff('blah')}",
262             "test foo/blah.cppblah /bar/ack.cppblah",
263
264             "test ${SOURCES.attribute.attr1}",
265             "test attr$1-blah.cpp attr$1-ack.cpp attr$1-ack.c",
266
267             "test ${SOURCES.attribute.attr2}",
268             "test attr$2-blah.cpp attr$2-ack.cpp attr$2-ack.c",
269
270             # Test adjacent expansions.
271             "foo$BAR",
272             "foobaz",
273
274             "foo${BAR}",
275             "foobaz",
276
277             # Test that adjacent expansions don't get re-interpreted
278             # together.  The correct disambiguated expansion should be:
279             #   $XXX$HHH => ${FFF}III => GGGIII
280             # not:
281             #   $XXX$HHH => ${FFFIII} => BADNEWS
282             "$XXX$HHH",             "GGGIII",
283
284             # Test double-dollar-sign behavior.
285             "$$FFF$HHH",            "$FFFIII",
286
287             # Test that a Literal will stop dollar-sign substitution.
288             "$XXX $LITERAL $FFF",   "GGG $XXX GGG",
289
290             # Test that we don't blow up even if they subscript
291             # something in ways they "can't."
292             "${FFF[0]}",            "G",
293             "${FFF[7]}",            "",
294             "${NOTHING[1]}",        "",
295
296             # Test various combinations of strings and lists.
297             #None,                   '',
298             '',                     '',
299             'x',                    'x',
300             'x y',                  'x y',
301             '$N',                   '',
302             '$X',                   'x',
303             '$Y',                   'x',
304             '$R',                   '',
305             '$S',                   'x y',
306             '$LS',                  'x y',
307             '$L',                   'x y',
308             '$TS',                  'x y',
309             '$T',                   'x y',
310             '$S z',                 'x y z',
311             '$LS z',                'x y z',
312             '$L z',                 'x y z',
313             '$TS z',                'x y z',
314             '$T z',                 'x y z',
315             #cs,                     'cs',
316             #cl,                     'cl',
317             '$CS',                  'cs',
318             '$CL',                  'cl',
319
320             # Test function calls within ${}.
321             '$FUNCCALL',            'a xc b',
322
323             # Bug reported by Christoph Wiedemann.
324             cvt('$xxx/bin'),        '/bin',
325
326             # Tests callables that don't match our calling arguments.
327             '$CALLABLE',            'callable-1',
328
329             # Test handling of quotes.
330             'aaa "bbb ccc" ddd',    'aaa "bbb ccc" ddd',
331         ]
332
333         kwargs = {'target' : target, 'source' : source,
334                   'gvars' : env.Dictionary()}
335
336         failed = 0
337         while cases:
338             input, expect = cases[:2]
339             expect = cvt(expect)
340             try:
341                 result = apply(scons_subst, (input, env), kwargs)
342             except Exception, e:
343                 print "    input %s generated %s %s" % (repr(input), e.__class__.__name__, str(e))
344                 failed = failed + 1
345             if result != expect:
346                 if failed == 0: print
347                 print "    input %s => %s did not match %s" % (repr(input), repr(result), repr(expect))
348                 failed = failed + 1
349             del cases[:2]
350         assert failed == 0, "%d subst() cases failed" % failed
351
352         # The expansion dictionary no longer comes from the construction
353         # environment automatically.
354         s = scons_subst('$AAA', env)
355         assert s == '', s
356
357         # Tests of the various SUBST_* modes of substitution.
358         subst_cases = [
359             "test $xxx",
360                 "test ",
361                 "test",
362                 "test",
363
364             "test $($xxx$)",
365                 "test $($)",
366                 "test",
367                 "test",
368
369             "test $( $xxx $)",
370                 "test $(  $)",
371                 "test",
372                 "test",
373
374             "$AAA ${AAA}A $BBBB $BBB",
375                 "a aA  b",
376                 "a aA b",
377                 "a aA b",
378
379             "$RECURSE",
380                "foo  bar",
381                "foo bar",
382                "foo bar",
383
384             "$RRR",
385                "foo  bar",
386                "foo bar",
387                "foo bar",
388
389             # Verify what happens with no target or source nodes.
390             "$TARGET $SOURCES",
391                 " ",
392                 "",
393                 "",
394
395             "$TARGETS $SOURCE",
396                 " ",
397                 "",
398                 "",
399
400             # Various tests refactored from ActionTests.py.
401             "${LIST}",
402                "This is $(  $) test",
403                "This is test",
404                "This is test",
405
406             ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
407                 "| $( a | b $) | c 1",
408                 "| a | b | c 1",
409                 "| | c 1",
410         ]
411
412         gvars = env.Dictionary()
413
414         failed = 0
415         while subst_cases:
416             input, eraw, ecmd, esig = subst_cases[:4]
417             result = scons_subst(input, env, mode=SUBST_RAW, gvars=gvars)
418             if result != eraw:
419                 if failed == 0: print
420                 print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
421                 failed = failed + 1
422             result = scons_subst(input, env, mode=SUBST_CMD, gvars=gvars)
423             if result != ecmd:
424                 if failed == 0: print
425                 print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
426                 failed = failed + 1
427             result = scons_subst(input, env, mode=SUBST_SIG, gvars=gvars)
428             if result != esig:
429                 if failed == 0: print
430                 print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
431                 failed = failed + 1
432             del subst_cases[:4]
433         assert failed == 0, "%d subst() mode cases failed" % failed
434
435         t1 = MyNode('t1')
436         t2 = MyNode('t2')
437         s1 = MyNode('s1')
438         s2 = MyNode('s2')
439         result = scons_subst("$TARGET $SOURCES", env,
440                                   target=[t1, t2],
441                                   source=[s1, s2])
442         assert result == "t1 s1 s2", result
443         result = scons_subst("$TARGET $SOURCES", env,
444                                   target=[t1, t2],
445                                   source=[s1, s2],
446                                   gvars={})
447         assert result == "t1 s1 s2", result
448
449         result = scons_subst("$TARGET $SOURCES", env, target=[], source=[])
450         assert result == " ", result
451         result = scons_subst("$TARGETS $SOURCE", env, target=[], source=[])
452         assert result == " ", result
453
454         # Test interpolating a callable.
455         newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS",
456                              env, target=MyNode('t'), source=MyNode('s'),
457                              gvars=gvars)
458         assert newcom == "test foo baz s t", newcom
459
460         # Test that we handle attribute errors during expansion as expected.
461         try:
462             class Foo:
463                 pass
464             scons_subst('${foo.bar}', env, gvars={'foo':Foo()})
465         except SCons.Errors.UserError, e:
466             expect = [
467                 "AttributeError `bar' trying to evaluate `${foo.bar}'",
468                 "AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
469                 "AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
470             ]
471             assert str(e) in expect, e
472         else:
473             raise AssertionError, "did not catch expected UserError"
474
475         # Test that we handle syntax errors during expansion as expected.
476         try:
477             scons_subst('$foo.bar.3.0', env)
478         except SCons.Errors.UserError, e:
479             expect = [
480                 # Python 1.5
481                 "SyntaxError `invalid syntax' trying to evaluate `$foo.bar.3.0'",
482                 # Python 2.2, 2.3, 2.4
483                 "SyntaxError `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'",
484                 # Python 2.5
485                 "SyntaxError `invalid syntax (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
486             ]
487             assert str(e) in expect, e
488         else:
489             raise AssertionError, "did not catch expected UserError"
490
491         # Test that we handle type errors 
492         try:
493             scons_subst("${NONE[2]}", env, gvars={'NONE':None})
494         except SCons.Errors.UserError, e:
495             expect = [
496                 # Python 1.5, 2.2, 2.3, 2.4
497                 "TypeError `unsubscriptable object' trying to evaluate `${NONE[2]}'",
498                 # Python 2.5 and later
499                 "TypeError `'NoneType' object is unsubscriptable' trying to evaluate `${NONE[2]}'",
500             ]
501             assert str(e) in expect, e
502         else:
503             raise AssertionError, "did not catch expected UserError"
504
505         try:
506             def func(a, b, c):
507                 pass
508             scons_subst("${func(1)}", env, gvars={'func':func})
509         except SCons.Errors.UserError, e:
510             expect = [
511                 # Python 1.5
512                 "TypeError `not enough arguments; expected 3, got 1' trying to evaluate `${func(1)}'",
513                 # Python 2.2, 2.3, 2.4, 2.5
514                 "TypeError `func() takes exactly 3 arguments (1 given)' trying to evaluate `${func(1)}'"
515             ]
516             assert str(e) in expect, repr(str(e))
517         else:
518             raise AssertionError, "did not catch expected UserError"
519
520         # Test that the combination of SUBST_RAW plus a pass-through
521         # conversion routine allows us to fetch a function through the
522         # dictionary.  CommandAction uses this to allow delayed evaluation
523         # of $SPAWN variables.
524         x = lambda x: x
525         r = scons_subst("$CALLABLE", env, mode=SUBST_RAW, conv=x, gvars=gvars)
526         assert r is callable_object, repr(r)
527         r = scons_subst("$CALLABLE", env, mode=SUBST_RAW, gvars=gvars)
528         assert r == 'callable-1', repr(r)
529
530         # Test how we handle overriding the internal conversion routines.
531         def s(obj):
532             return obj
533
534         n1 = MyNode('n1')
535         env = DummyEnv({'NODE' : n1})
536         gvars = env.Dictionary()
537         node = scons_subst("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
538         assert node is n1, node
539         node = scons_subst("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
540         assert node is n1, node
541         node = scons_subst("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
542         assert node is n1, node
543
544         # Test returning a function.
545         #env = DummyEnv({'FUNCTION' : foo})
546         #gvars = env.Dictionary()
547         #func = scons_subst("$FUNCTION", env, mode=SUBST_RAW, call=None, gvars=gvars)
548         #assert func is function_foo, func
549         #func = scons_subst("$FUNCTION", env, mode=SUBST_CMD, call=None, gvars=gvars)
550         #assert func is function_foo, func
551         #func = scons_subst("$FUNCTION", env, mode=SUBST_SIG, call=None, gvars=gvars)
552         #assert func is function_foo, func
553
554         # Test supplying an overriding gvars dictionary.
555         env = DummyEnv({'XXX' : 'xxx'})
556         result = scons_subst('$XXX', env, gvars=env.Dictionary())
557         assert result == 'xxx', result
558         result = scons_subst('$XXX', env, gvars={'XXX' : 'yyy'})
559         assert result == 'yyy', result
560
561     def test_CLVar(self):
562         """Test scons_subst() and scons_subst_list() with CLVar objects"""
563
564         loc = {}
565         loc['FOO'] = 'foo'
566         loc['BAR'] = SCons.Util.CLVar('bar')
567         loc['CALL'] = lambda target, source, env, for_signature: 'call'
568         env = DummyEnv(loc)
569
570         cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
571
572         newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
573         assert newcmd == 'test foo bar call test', newcmd
574
575         cmd_list = scons_subst_list(cmd, env, gvars=env.Dictionary())
576         assert len(cmd_list) == 1, cmd_list
577         assert cmd_list[0][0] == "test", cmd_list[0][0]
578         assert cmd_list[0][1] == "foo", cmd_list[0][1]
579         assert cmd_list[0][2] == "bar", cmd_list[0][2]
580         assert cmd_list[0][3] == "call", cmd_list[0][3]
581         assert cmd_list[0][4] == "test", cmd_list[0][4]
582
583     def test_subst_list(self):
584         """Testing the scons_subst_list() method..."""
585         class MyNode(DummyNode):
586             """Simple node work-alike with some extra stuff for testing."""
587             def __init__(self, name):
588                 DummyNode.__init__(self, name)
589                 class Attribute:
590                     pass
591                 self.attribute = Attribute()
592                 self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
593                 self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
594
595         class TestCallable:
596             def __init__(self, value):
597                 self.value = value
598             def __call__(self):
599                 pass
600             def __str__(self):
601                 return self.value
602
603         target = [ MyNode("./foo/bar.exe"),
604                    MyNode("/bar/baz with spaces.obj"),
605                    MyNode("../foo/baz.obj") ]
606         source = [ MyNode("./foo/blah with spaces.cpp"),
607                    MyNode("/bar/ack.cpp"),
608                    MyNode("../foo/ack.c") ]
609
610         callable_object = TestCallable('callable-2')
611
612         def _defines(defs):
613             l = []
614             for d in defs:
615                 if SCons.Util.is_List(d) or type(d) is types.TupleType:
616                     l.append(str(d[0]) + '=' + str(d[1]))
617                 else:
618                     l.append(str(d))
619             return l
620
621         loc = {
622             'xxx'       : None,
623             'NEWLINE'   : 'before\nafter',
624
625             'AAA'       : 'a',
626             'BBB'       : 'b',
627             'CCC'       : 'c',
628
629             'DO'        : DummyNode('do something'),
630             'FOO'       : DummyNode('foo.in'),
631             'BAR'       : DummyNode('bar with spaces.out'),
632             'CRAZY'     : DummyNode('crazy\nfile.in'),
633
634             # $XXX$HHH should expand to GGGIII, not BADNEWS.
635             'XXX'       : '$FFF',
636             'FFF'       : 'GGG',
637             'HHH'       : 'III',
638             'FFFIII'    : 'BADNEWS',
639
640             'CMDGEN1'   : CmdGen1,
641             'CMDGEN2'   : CmdGen2,
642
643             'LITERALS'  : [ Literal('foo\nwith\nnewlines'),
644                             Literal('bar\nwith\nnewlines') ],
645
646             # Test various combinations of strings, lists and functions.
647             'N'         : None,
648             'X'         : 'x',
649             'Y'         : '$X',
650             'R'         : '$R',
651             'S'         : 'x y',
652             'LS'        : ['x y'],
653             'L'         : ['x', 'y'],
654             'CS'        : cs,
655             'CL'        : cl,
656
657             # Test function calls within ${}.
658             'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
659             'FUNC1'     : lambda x: x,
660             'FUNC2'     : lambda target, source, env, for_signature: ['x$CCC'],
661
662             # Various tests refactored from ActionTests.py.
663             'LIST'      : [["This", "is", "$(", "$a", "$)", "test"]],
664
665             # Test recursion.
666             'RECURSE'   : 'foo $RECURSE bar',
667             'RRR'       : 'foo $SSS bar',
668             'SSS'       : '$RRR',
669
670             # Test callable objects that don't match our calling arguments.
671             'CALLABLE'  : callable_object,
672
673             '_defines'  : _defines,
674             'DEFS'      : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ],
675         }
676
677         env = DummyEnv(loc)
678
679         cases = [
680             "$TARGETS",
681             [
682                 ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
683             ],
684
685             "$SOURCES $NEWLINE $TARGETS",
686             [
687                 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
688                 ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
689             ],
690
691             "$SOURCES$NEWLINE",
692             [
693                 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
694                 ["after"],
695             ],
696
697             "foo$FFF",
698             [
699                 ["fooGGG"],
700             ],
701
702             "foo${FFF}",
703             [
704                 ["fooGGG"],
705             ],
706
707             "test ${SOURCES.attribute.attr1}",
708             [
709                 ["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"],
710             ],
711
712             "test ${SOURCES.attribute.attr2}",
713             [
714                 ["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"],
715             ],
716
717             "$DO --in=$FOO --out=$BAR",
718             [
719                 ["do something", "--in=foo.in", "--out=bar with spaces.out"],
720             ],
721
722             # This test is now fixed, and works like it should.
723             "$DO --in=$CRAZY --out=$BAR",
724             [
725                 ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
726             ],
727
728             # Try passing a list to scons_subst_list().
729             [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
730             [
731                 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
732                 ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
733             ],
734
735             # Test against a former bug in scons_subst_list().
736             "$XXX$HHH",
737             [
738                 ["GGGIII"],
739             ],
740
741             # Test double-dollar-sign behavior.
742             "$$FFF$HHH",
743             [
744                 ["$FFFIII"],
745             ],
746
747             # Test various combinations of strings, lists and functions.
748             None,                   [[]],
749             [None],                 [[]],
750             '',                     [[]],
751             [''],                   [[]],
752             'x',                    [['x']],
753             ['x'],                  [['x']],
754             'x y',                  [['x', 'y']],
755             ['x y'],                [['x y']],
756             ['x', 'y'],             [['x', 'y']],
757             '$N',                   [[]],
758             ['$N'],                 [[]],
759             '$X',                   [['x']],
760             ['$X'],                 [['x']],
761             '$Y',                   [['x']],
762             ['$Y'],                 [['x']],
763             #'$R',                   [[]],
764             #['$R'],                 [[]],
765             '$S',                   [['x', 'y']],
766             '$S z',                 [['x', 'y', 'z']],
767             ['$S'],                 [['x', 'y']],
768             ['$S z'],               [['x', 'y z']],     # XXX - IS THIS BEST?
769             ['$S', 'z'],            [['x', 'y', 'z']],
770             '$LS',                  [['x y']],
771             '$LS z',                [['x y', 'z']],
772             ['$LS'],                [['x y']],
773             ['$LS z'],              [['x y z']],
774             ['$LS', 'z'],           [['x y', 'z']],
775             '$L',                   [['x', 'y']],
776             '$L z',                 [['x', 'y', 'z']],
777             ['$L'],                 [['x', 'y']],
778             ['$L z'],               [['x', 'y z']],     # XXX - IS THIS BEST?
779             ['$L', 'z'],            [['x', 'y', 'z']],
780             cs,                     [['cs']],
781             [cs],                   [['cs']],
782             cl,                     [['cl']],
783             [cl],                   [['cl']],
784             '$CS',                  [['cs']],
785             ['$CS'],                [['cs']],
786             '$CL',                  [['cl']],
787             ['$CL'],                [['cl']],
788
789             # Test function calls within ${}.
790             '$FUNCCALL',            [['a', 'xc', 'b']],
791
792             # Test handling of newlines in white space.
793             'foo\nbar',             [['foo'], ['bar']],
794             'foo\n\nbar',           [['foo'], ['bar']],
795             'foo \n \n bar',        [['foo'], ['bar']],
796             'foo \nmiddle\n bar',   [['foo'], ['middle'], ['bar']],
797
798             # Bug reported by Christoph Wiedemann.
799             cvt('$xxx/bin'),        [['/bin']],
800
801             # Test variables smooshed together with different prefixes.
802             'foo$AAA',              [['fooa']],
803             '<$AAA',                [['<', 'a']],
804             '>$AAA',                [['>', 'a']],
805             '|$AAA',                [['|', 'a']],
806
807             # Test callables that don't match our calling arguments.
808             '$CALLABLE',            [['callable-2']],
809
810             # Test handling of quotes.
811             # XXX Find a way to handle this in the future.
812             #'aaa "bbb ccc" ddd',    [['aaa', 'bbb ccc', 'ddd']],
813
814             '${_defines(DEFS)}',     [['Q1="q1"', 'Q2="a"']],
815         ]
816
817         gvars = env.Dictionary()
818
819         kwargs = {'target' : target, 'source' : source, 'gvars' : gvars}
820
821         failed = 0
822         while cases:
823             input, expect = cases[:2]
824             expect = map(lambda l: map(cvt, l), expect)
825             result = apply(scons_subst_list, (input, env), kwargs)
826             if result != expect:
827                 if failed == 0: print
828                 print "    input %s => %s did not match %s" % (repr(input), result, repr(expect))
829                 failed = failed + 1
830             del cases[:2]
831         assert failed == 0, "%d subst_list() cases failed" % failed
832
833         # The expansion dictionary no longer comes from the construction
834         # environment automatically.
835         s = scons_subst_list('$AAA', env)
836         assert s == [[]], s
837
838         t1 = MyNode('t1')
839         t2 = MyNode('t2')
840         s1 = MyNode('s1')
841         s2 = MyNode('s2')
842         result = scons_subst_list("$TARGET $SOURCES", env,
843                                   target=[t1, t2],
844                                   source=[s1, s2],
845                                   gvars=gvars)
846         assert result == [['t1', 's1', 's2']], result
847         result = scons_subst_list("$TARGET $SOURCES", env,
848                                   target=[t1, t2],
849                                   source=[s1, s2],
850                                   gvars={})
851         assert result == [['t1', 's1', 's2']], result
852
853         # Test interpolating a callable.
854         _t = DummyNode('t')
855         _s = DummyNode('s')
856         cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES",
857                                     env, target=_t, source=_s,
858                                     gvars=gvars)
859         assert cmd_list == [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list
860
861         # Test escape functionality.
862         def escape_func(foo):
863             return '**' + foo + '**'
864         cmd_list = scons_subst_list("abc $LITERALS xyz", env, gvars=gvars)
865         assert cmd_list == [['abc',
866                              'foo\nwith\nnewlines',
867                              'bar\nwith\nnewlines',
868                              'xyz']], cmd_list
869         c = cmd_list[0][0].escape(escape_func)
870         assert c == 'abc', c
871         c = cmd_list[0][1].escape(escape_func)
872         assert c == '**foo\nwith\nnewlines**', c
873         c = cmd_list[0][2].escape(escape_func)
874         assert c == '**bar\nwith\nnewlines**', c
875         c = cmd_list[0][3].escape(escape_func)
876         assert c == 'xyz', c
877
878         # We used to treat literals smooshed together like the whole
879         # thing was literal and escape it as a unit.  The commented-out
880         # asserts below are in case we ever have to find a way to
881         # resurrect that functionality in some way.
882         cmd_list = scons_subst_list("abc${LITERALS}xyz", env, gvars=gvars)
883         c = cmd_list[0][0].escape(escape_func)
884         #assert c == '**abcfoo\nwith\nnewlines**', c
885         assert c == 'abcfoo\nwith\nnewlines', c
886         c = cmd_list[0][1].escape(escape_func)
887         #assert c == '**bar\nwith\nnewlinesxyz**', c
888         assert c == 'bar\nwith\nnewlinesxyz', c
889
890         cmd_list = scons_subst_list('echo "target: $TARGET"', env,
891                                     target=_t, gvars=gvars)
892         c = cmd_list[0][0].escape(escape_func)
893         assert c == 'echo', c
894         c = cmd_list[0][1].escape(escape_func)
895         assert c == '"target:', c
896         c = cmd_list[0][2].escape(escape_func)
897         assert c == 't"', c
898
899         # Tests of the various SUBST_* modes of substitution.
900         subst_list_cases = [
901             "test $xxx",
902                 [["test"]],
903                 [["test"]],
904                 [["test"]],
905
906             "test $($xxx$)",
907                 [["test", "$($)"]],
908                 [["test"]],
909                 [["test"]],
910
911             "test $( $xxx $)",
912                 [["test", "$(", "$)"]],
913                 [["test"]],
914                 [["test"]],
915
916             "$AAA ${AAA}A $BBBB $BBB",
917                 [["a", "aA", "b"]],
918                 [["a", "aA", "b"]],
919                 [["a", "aA", "b"]],
920
921             "$RECURSE",
922                 [["foo", "bar"]],
923                 [["foo", "bar"]],
924                 [["foo", "bar"]],
925
926             "$RRR",
927                 [["foo", "bar"]],
928                 [["foo", "bar"]],
929                 [["foo", "bar"]],
930
931             # Verify what happens with no target or source nodes.
932             "$TARGET $SOURCES",
933                 [[]],
934                 [[]],
935                 [[]],
936
937             "$TARGETS $SOURCE",
938                 [[]],
939                 [[]],
940                 [[]],
941
942             # Various test refactored from ActionTests.py
943             "${LIST}",
944                 [['This', 'is', '$(', '$)', 'test']],
945                 [['This', 'is', 'test']],
946                 [['This', 'is', 'test']],
947
948             ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
949                 [["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]],
950                 [["|", "a", "|", "b", "|", "c", "1"]],
951                 [["|", "|", "c", "1"]],
952         ]
953
954         gvars = env.Dictionary()
955
956         r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW, gvars=gvars)
957         assert r == [[]], r
958
959         failed = 0
960         while subst_list_cases:
961             input, eraw, ecmd, esig = subst_list_cases[:4]
962             result = scons_subst_list(input, env, mode=SUBST_RAW, gvars=gvars)
963             if result != eraw:
964                 if failed == 0: print
965                 print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
966                 failed = failed + 1
967             result = scons_subst_list(input, env, mode=SUBST_CMD, gvars=gvars)
968             if result != ecmd:
969                 if failed == 0: print
970                 print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
971                 failed = failed + 1
972             result = scons_subst_list(input, env, mode=SUBST_SIG, gvars=gvars)
973             if result != esig:
974                 if failed == 0: print
975                 print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
976                 failed = failed + 1
977             del subst_list_cases[:4]
978         assert failed == 0, "%d subst() mode cases failed" % failed
979
980         # Test that we handle attribute errors during expansion as expected.
981         try:
982             class Foo:
983                 pass
984             scons_subst_list('${foo.bar}', env, gvars={'foo':Foo()})
985         except SCons.Errors.UserError, e:
986             expect = [
987                 "AttributeError `bar' trying to evaluate `${foo.bar}'",
988                 "AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
989                 "AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
990             ]
991             assert str(e) in expect, e
992         else:
993             raise AssertionError, "did not catch expected UserError"
994
995         # Test that we handle syntax errors during expansion as expected.
996         try:
997             scons_subst_list('$foo.bar.3.0', env)
998         except SCons.Errors.UserError, e:
999             expect = [
1000                 "SyntaxError `invalid syntax' trying to evaluate `$foo.bar.3.0'",
1001                 "SyntaxError `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'",
1002                 "SyntaxError `invalid syntax (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
1003             ]
1004             assert str(e) in expect, e
1005         else:
1006             raise AssertionError, "did not catch expected SyntaxError"
1007
1008         # Test that the combination of SUBST_RAW plus a pass-through
1009         # conversion routine allows us to fetch a function through the
1010         # dictionary.
1011         x = lambda x: x
1012         r = scons_subst_list("$CALLABLE", env, mode=SUBST_RAW, conv=x, gvars=gvars)
1013         assert r == [[callable_object]], repr(r)
1014         r = scons_subst_list("$CALLABLE", env, mode=SUBST_RAW, gvars=gvars)
1015         assert r == [['callable-2']], repr(r)
1016
1017         # Test we handle overriding the internal conversion routines.
1018         def s(obj):
1019             return obj
1020
1021         n1 = MyNode('n1')
1022         env = DummyEnv({'NODE' : n1})
1023         gvars=env.Dictionary()
1024         node = scons_subst_list("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
1025         assert node == [[n1]], node
1026         node = scons_subst_list("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
1027         assert node == [[n1]], node
1028         node = scons_subst_list("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
1029         assert node == [[n1]], node
1030
1031         # Test supplying an overriding gvars dictionary.
1032         env = DummyEnv({'XXX' : 'xxx'})
1033         result = scons_subst_list('$XXX', env, gvars=env.Dictionary())
1034         assert result == [['xxx']], result
1035         result = scons_subst_list('$XXX', env, gvars={'XXX' : 'yyy'})
1036         assert result == [['yyy']], result
1037
1038     def test_subst_once(self):
1039         """Testing the scons_subst_once() method"""
1040
1041         loc = {
1042             'CCFLAGS'           : '-DFOO',
1043             'ONE'               : 1,
1044             'RECURSE'           : 'r $RECURSE r',
1045             'LIST'              : ['a', 'b', 'c'],
1046         }
1047
1048         env = DummyEnv(loc)
1049
1050         cases = [
1051             '$CCFLAGS -DBAR',
1052             'OTHER_KEY',
1053             '$CCFLAGS -DBAR',
1054
1055             '$CCFLAGS -DBAR',
1056             'CCFLAGS',
1057             '-DFOO -DBAR',
1058
1059             'x $ONE y',
1060             'ONE',
1061             'x 1 y',
1062
1063             'x $RECURSE y',
1064             'RECURSE',
1065             'x r $RECURSE r y',
1066
1067             '$LIST',
1068             'LIST',
1069             'a b c',
1070
1071             ['$LIST'],
1072             'LIST',
1073             ['a', 'b', 'c'],
1074
1075             ['x', '$LIST', 'y'],
1076             'LIST',
1077             ['x', 'a', 'b', 'c', 'y'],
1078
1079             ['x', 'x $LIST y', 'y'],
1080             'LIST',
1081             ['x', 'x a b c y', 'y'],
1082
1083             ['x', 'x $CCFLAGS y', 'y'],
1084             'LIST',
1085             ['x', 'x $CCFLAGS y', 'y'],
1086
1087             ['x', 'x $RECURSE y', 'y'],
1088             'LIST',
1089             ['x', 'x $RECURSE y', 'y'],
1090         ]
1091
1092         failed = 0
1093         while cases:
1094             input, key, expect = cases[:3]
1095             result = scons_subst_once(input, env, key)
1096             if result != expect:
1097                 if failed == 0: print
1098                 print "    input %s (%s) => %s did not match %s" % (repr(input), repr(key), repr(result), repr(expect))
1099                 failed = failed + 1
1100             del cases[:3]
1101         assert failed == 0, "%d subst() cases failed" % failed
1102
1103     def test_quote_spaces(self):
1104         """Testing the quote_spaces() method..."""
1105         q = quote_spaces('x')
1106         assert q == 'x', q
1107
1108         q = quote_spaces('x x')
1109         assert q == '"x x"', q
1110
1111         q = quote_spaces('x\tx')
1112         assert q == '"x\tx"', q
1113
1114     class Node:
1115         def __init__(self, name, children=[]):
1116             self.children = children
1117             self.name = name
1118         def __str__(self):
1119             return self.name
1120         def exists(self):
1121             return 1
1122         def rexists(self):
1123             return 1
1124         def has_builder(self):
1125             return 1
1126         def has_explicit_builder(self):
1127             return 1
1128         def side_effect(self):
1129             return 1
1130         def precious(self):
1131             return 1
1132         def always_build(self):
1133             return 1
1134         def current(self):
1135             return 1
1136
1137     def test_Literal(self):
1138         """Test the Literal() function."""
1139         input_list = [ '$FOO', Literal('$BAR') ]
1140         gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
1141
1142         def escape_func(cmd):
1143             return '**' + cmd + '**'
1144
1145         cmd_list = scons_subst_list(input_list, None, gvars=gvars)
1146         cmd_list = escape_list(cmd_list[0], escape_func)
1147         assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
1148
1149     def test_SpecialAttrWrapper(self):
1150         """Test the SpecialAttrWrapper() function."""
1151         input_list = [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
1152         gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
1153
1154         def escape_func(cmd):
1155             return '**' + cmd + '**'
1156
1157         cmd_list = scons_subst_list(input_list, None, gvars=gvars)
1158         cmd_list = escape_list(cmd_list[0], escape_func)
1159         assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
1160
1161         cmd_list = scons_subst_list(input_list, None, mode=SUBST_SIG, gvars=gvars)
1162         cmd_list = escape_list(cmd_list[0], escape_func)
1163         assert cmd_list == ['BAZ', '**BLEH**'], cmd_list
1164
1165     def test_subst_dict(self):
1166         """Test substituting dictionary values in an Action
1167         """
1168         t = DummyNode('t')
1169         s = DummyNode('s')
1170         d = subst_dict(target=t, source=s)
1171         assert str(d['TARGETS'][0]) == 't', d['TARGETS']
1172         assert str(d['TARGET']) == 't', d['TARGET']
1173         assert str(d['SOURCES'][0]) == 's', d['SOURCES']
1174         assert str(d['SOURCE']) == 's', d['SOURCE']
1175
1176         t1 = DummyNode('t1')
1177         t2 = DummyNode('t2')
1178         s1 = DummyNode('s1')
1179         s2 = DummyNode('s2')
1180         d = subst_dict(target=[t1, t2], source=[s1, s2])
1181         TARGETS = map(lambda x: str(x), d['TARGETS'])
1182         TARGETS.sort()
1183         assert TARGETS == ['t1', 't2'], d['TARGETS']
1184         assert str(d['TARGET']) == 't1', d['TARGET']
1185         SOURCES = map(lambda x: str(x), d['SOURCES'])
1186         SOURCES.sort()
1187         assert SOURCES == ['s1', 's2'], d['SOURCES']
1188         assert str(d['SOURCE']) == 's1', d['SOURCE']
1189
1190         class V:
1191             # Fake Value node with no rfile() method.
1192             def __init__(self, name):
1193                 self.name = name
1194             def __str__(self):
1195                 return 'v-'+self.name
1196             def get_subst_proxy(self):
1197                 return self
1198
1199         class N(V):
1200             def rfile(self):
1201                 return self.__class__('rstr-' + self.name)
1202
1203         t3 = N('t3')
1204         t4 = DummyNode('t4')
1205         t5 = V('t5')
1206         s3 = DummyNode('s3')
1207         s4 = N('s4')
1208         s5 = V('s5')
1209         d = subst_dict(target=[t3, t4, t5], source=[s3, s4, s5])
1210         TARGETS = map(lambda x: str(x), d['TARGETS'])
1211         TARGETS.sort()
1212         assert TARGETS == ['t4', 'v-t3', 'v-t5'], TARGETS
1213         SOURCES = map(lambda x: str(x), d['SOURCES'])
1214         SOURCES.sort()
1215         assert SOURCES == ['s3', 'v-rstr-s4', 'v-s5'], SOURCES
1216
1217 if __name__ == "__main__":
1218     suite = unittest.makeSuite(SubstTestCase, 'test_')
1219     if not unittest.TextTestRunner().run(suite).wasSuccessful():
1220         sys.exit(1)