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