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