3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 from functools
import partial
33 from SCons
.Subst
import (Literal
, SUBST_CMD
, SUBST_RAW
, SUBST_SIG
, SpecialAttrWrapper
, collections
,
34 escape_list
, quote_spaces
, scons_subst
, scons_subst_list
, scons_subst_once
,
38 """Simple node work-alike."""
39 def __init__(self
, name
):
40 self
.name
= os
.path
.normpath(name
)
47 def get_subst_proxy(self
):
51 def __init__(self
, dict={}):
54 def Dictionary(self
, key
= None):
59 def __getitem__(self
, key
):
62 def get(self
, key
, default
):
63 return self
.dict.get(key
, default
)
66 dict = self
.dict.copy()
67 dict["TARGETS"] = 'tsig'
68 dict["SOURCES"] = 'ssig'
71 def cs(target
=None, source
=None, env
=None, for_signature
=None):
74 def cl(target
=None, source
=None, env
=None, for_signature
=None):
77 def CmdGen1(target
, source
, env
, for_signature
):
78 # Nifty trick...since Environment references are interpolated,
79 # instantiate an instance of a callable class with this one,
80 # which will then get evaluated.
81 assert str(target
) == 't', target
82 assert str(source
) == 's', source
83 return "${CMDGEN2('foo', %d)}" % for_signature
86 def __init__(self
, mystr
, forsig
):
88 self
.expect_for_signature
= forsig
90 def __call__(self
, target
, source
, env
, for_signature
):
91 assert str(target
) == 't', target
92 assert str(source
) == 's', source
93 assert for_signature
== self
.expect_for_signature
, for_signature
94 return [ self
.mystr
, env
.Dictionary('BAR') ]
97 def CallableWithDefault(target
, source
, env
, for_signature
, other_value
="default"):
98 assert str(target
) == 't', target
99 assert str(source
) == 's', source
100 return "CallableWithDefault: %s"%other
_value
102 PartialCallable
= partial(CallableWithDefault
, other_value
="partial")
104 def CallableWithNoDefault(target
, source
, env
, for_signature
, other_value
):
105 assert str(target
) == 't', target
106 assert str(source
) == 's', source
107 return "CallableWithNoDefault: %s"%other
_value
109 PartialCallableNoDefault
= partial(CallableWithNoDefault
, other_value
="partialNoDefault")
118 return str.replace('/', os
.sep
)
120 class SubstTestCase(unittest
.TestCase
):
121 class MyNode(DummyNode
):
122 """Simple node work-alike with some extra stuff for testing."""
123 def __init__(self
, name
):
124 super().__init
__(name
)
127 self
.attribute
= Attribute()
128 self
.attribute
.attr1
= 'attr$1-' + os
.path
.basename(name
)
129 self
.attribute
.attr2
= 'attr$2-' + os
.path
.basename(name
)
130 def get_stuff(self
, extra
):
131 return self
.name
+ extra
135 def __init__(self
, literal
):
136 self
.literal
= literal
139 def is_literal(self
):
143 def __init__(self
, value
):
150 # only use of this is currently commented out below
151 #def function_foo(arg):
154 target
= [ MyNode("./foo/bar.exe"),
155 MyNode("/bar/baz with spaces.obj"),
156 MyNode("../foo/baz.obj") ]
157 source
= [ MyNode("./foo/blah with spaces.cpp"),
158 MyNode("/bar/ack.cpp"),
159 MyNode("../foo/ack.c") ]
161 callable_object_1
= TestCallable('callable-1')
162 callable_object_2
= TestCallable('callable-2')
167 if SCons
.Util
.is_List(d
) or isinstance(d
, tuple):
168 l
.append(str(d
[0]) + '=' + str(d
[1]))
175 'NEWLINE' : 'before\nafter',
189 'DO' : DummyNode('do something'),
190 'FOO' : DummyNode('foo.in'),
191 'BAR' : DummyNode('bar with spaces.out'),
192 'CRAZY' : DummyNode('crazy\nfile.in'),
194 # $XXX$HHH should expand to GGGIII, not BADNEWS.
198 'FFFIII' : 'BADNEWS',
200 'THING1' : "$(STUFF$)",
201 'THING2' : "$THING1",
203 'LITERAL' : TestLiteral("$XXX"),
205 # Test that we can expand to and return a function.
206 #'FUNCTION' : function_foo,
211 'CallableWithDefault': CallableWithDefault
,
212 'PartialCallable' : PartialCallable
,
213 'PartialCallableNoDefault' : PartialCallableNoDefault
,
215 'LITERALS' : [ Literal('foo\nwith\nnewlines'),
216 Literal('bar\nwith\nnewlines') ],
221 # Test various combinations of strings, lists and functions.
233 'US' : collections
.UserString('us'),
235 # Test function calls within ${}.
236 'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}',
237 'FUNC1' : lambda x
: x
,
238 'FUNC2' : lambda target
, source
, env
, for_signature
: ['x$CCC'],
240 # Various tests refactored from ActionTests.py.
241 'LIST' : [["This", "is", "$(", "$a", "$)", "test"]],
244 'RECURSE' : 'foo $RECURSE bar',
245 'RRR' : 'foo $SSS bar',
248 # Test callables that don't match the calling arguments.
249 'CALLABLE1' : callable_object_1
,
250 'CALLABLE2' : callable_object_2
,
252 '_defines' : _defines
,
253 'DEFS' : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ],
256 def basic_comparisons(self
, function
, convert
):
257 env
= DummyEnv(self
.loc
)
258 cases
= self
.basic_cases
[:]
259 kwargs
= {'target' : self
.target
, 'source' : self
.source
,
260 'gvars' : env
.Dictionary()}
265 input, expect
= cases
[:2]
266 expect
= convert(expect
)
268 result
= function(input, env
, **kwargs
)
269 except Exception as e
:
270 fmt
= " input %s generated %s (%s)"
271 print(fmt
% (repr(input), e
.__class
__.__name
__, repr(e
)))
275 if failed
== 0: print()
276 print("[%4d] input %s => \n%s did not match \n%s" % (case_count
, repr(input), repr(result
), repr(expect
)))
280 fmt
= "%d %s() cases failed"
281 assert failed
== 0, fmt
% (failed
, function
.__name
__)
284 class scons_subst_TestCase(SubstTestCase
):
286 # Basic tests of substitution functionality.
288 # Basics: strings without expansions are left alone, and
289 # the simplest possible expansion to a null-string value.
293 # Test expansion of integer values.
294 "test $zero", "test 0",
295 "test $one", "test 1",
297 # Test multiple re-expansion of values.
298 "test $ONE", "test four",
300 # Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions.
301 "test $TARGETS $SOURCES",
302 "test foo/bar.exe /bar/baz with spaces.obj ../foo/baz.obj foo/blah with spaces.cpp /bar/ack.cpp ../foo/ack.c",
304 "test ${TARGETS[:]} ${SOURCES[0]}",
305 "test foo/bar.exe /bar/baz with spaces.obj ../foo/baz.obj foo/blah with spaces.cpp",
307 "test ${TARGETS[1:]}v",
308 "test /bar/baz with spaces.obj ../foo/baz.objv",
313 "test $TARGET$NO_SUCH_VAR[0]",
314 "test foo/bar.exe[0]",
319 "test ${SOURCES[0:2].foo}",
325 "test ${TARGET.get_stuff('blah')}",
326 "test foo/bar.exeblah",
328 "test ${SOURCES.get_stuff('blah')}",
329 "test foo/blah with spaces.cppblah /bar/ack.cppblah ../foo/ack.cblah",
331 "test ${SOURCES[0:2].get_stuff('blah')}",
332 "test foo/blah with spaces.cppblah /bar/ack.cppblah",
334 "test ${SOURCES[0:2].get_stuff('blah')}",
335 "test foo/blah with spaces.cppblah /bar/ack.cppblah",
337 "test ${SOURCES.attribute.attr1}",
338 "test attr$1-blah with spaces.cpp attr$1-ack.cpp attr$1-ack.c",
340 "test ${SOURCES.attribute.attr2}",
341 "test attr$2-blah with spaces.cpp attr$2-ack.cpp attr$2-ack.c",
343 # Test adjacent expansions.
350 # Test that adjacent expansions don't get re-interpreted
351 # together. The correct disambiguated expansion should be:
352 # $XXX$HHH => ${FFF}III => GGGIII
354 # $XXX$HHH => ${FFFIII} => BADNEWS
355 "$XXX$HHH", "GGGIII",
357 # Test double-dollar-sign behavior.
358 "$$FFF$HHH", "$FFFIII",
360 # Test double-dollar-sign before open paren. It's not meant
361 # to be signature escaping
362 'echo $$(pwd) > XYZ', 'echo $(pwd) > XYZ',
364 # Test that a Literal will stop dollar-sign substitution.
365 "$XXX $LITERAL $FFF", "GGG $XXX GGG",
367 # Test that we don't blow up even if they subscript
368 # something in ways they "can't."
373 # Test various combinations of strings and lists.
397 # Various uses of UserString.
398 collections
.UserString('x'), 'x',
399 collections
.UserString('$X'), 'x',
400 collections
.UserString('$US'), 'us',
403 # Test function calls within ${}.
404 '$FUNCCALL', 'a xc b',
406 # Bug reported by Christoph Wiedemann.
407 cvt('$xxx/bin'), '/bin',
409 # Tests callables that don't match our calling arguments.
410 '$CALLABLE1', 'callable-1',
412 # Test handling of quotes.
413 'aaa "bbb ccc" ddd', 'aaa "bbb ccc" ddd',
416 def test_scons_subst(self
):
417 """Test scons_subst(): basic substitution"""
418 return self
.basic_comparisons(scons_subst
, cvt
)
436 "test $( $THING2 $)",
437 "test $( $(STUFF$) $)",
441 "$AAA ${AAA}A $BBBB $BBB",
456 # Verify what happens with no target or source nodes.
467 # Various tests refactored from ActionTests.py.
469 "This is $( $) test",
473 ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
474 ["|", "$(", "a", "|", "b", "$)", "|", "c", "1"],
475 ["|", "a", "|", "b", "|", "c", "1"],
476 ["|", "|", "c", "1"],
479 def test_subst_env(self
):
480 """Test scons_subst(): expansion dictionary"""
481 # The expansion dictionary no longer comes from the construction
482 # environment automatically.
483 env
= DummyEnv(self
.loc
)
484 s
= scons_subst('$AAA', env
)
487 def test_subst_SUBST_modes(self
):
488 """Test scons_subst(): SUBST_* modes"""
489 env
= DummyEnv(self
.loc
)
490 subst_cases
= self
.subst_cases
[:]
492 gvars
= env
.Dictionary()
496 input, eraw
, ecmd
, esig
= subst_cases
[:4]
497 result
= scons_subst(input, env
, mode
=SUBST_RAW
, gvars
=gvars
)
499 if failed
== 0: print()
500 print(" input %s => RAW %s did not match %s" % (repr(input), repr(result
), repr(eraw
)))
502 result
= scons_subst(input, env
, mode
=SUBST_CMD
, gvars
=gvars
)
504 if failed
== 0: print()
505 print(" input %s => CMD %s did not match %s" % (repr(input), repr(result
), repr(ecmd
)))
507 result
= scons_subst(input, env
, mode
=SUBST_SIG
, gvars
=gvars
)
509 if failed
== 0: print()
510 print(" input %s => SIG %s did not match %s" % (repr(input), repr(result
), repr(esig
)))
513 assert failed
== 0, "%d subst() mode cases failed" % failed
515 def test_subst_target_source(self
):
516 """Test scons_subst(): target= and source= arguments"""
517 env
= DummyEnv(self
.loc
)
518 t1
= self
.MyNode('t1')
519 t2
= self
.MyNode('t2')
520 s1
= self
.MyNode('s1')
521 s2
= self
.MyNode('s2')
522 result
= scons_subst("$TARGET $SOURCES", env
,
525 assert result
== "t1 s1 s2", result
526 result
= scons_subst("$TARGET $SOURCES", env
,
530 assert result
== "t1 s1 s2", result
532 result
= scons_subst("$TARGET $SOURCES", env
, target
=[], source
=[])
533 assert result
== " ", result
534 result
= scons_subst("$TARGETS $SOURCE", env
, target
=[], source
=[])
535 assert result
== " ", result
537 def test_subst_callable_expansion(self
):
538 """Test scons_subst(): expanding a callable"""
539 env
= DummyEnv(self
.loc
)
540 gvars
= env
.Dictionary()
541 newcom
= scons_subst("test $CMDGEN1 $SOURCES $TARGETS", env
,
542 target
=self
.MyNode('t'), source
=self
.MyNode('s'),
544 assert newcom
== "test foo bar with spaces.out s t", newcom
546 def test_subst_callable_with_default_expansion(self
):
547 """Test scons_subst(): expanding a callable with a default value arg"""
548 env
= DummyEnv(self
.loc
)
549 gvars
= env
.Dictionary()
550 newcom
= scons_subst("test $CallableWithDefault $SOURCES $TARGETS", env
,
551 target
=self
.MyNode('t'), source
=self
.MyNode('s'),
553 assert newcom
== "test CallableWithDefault: default s t", newcom
555 def test_subst_partial_callable_with_default_expansion(self
):
556 """Test scons_subst(): expanding a functools.partial callable which sets
557 the default value in the callable"""
558 env
= DummyEnv(self
.loc
)
559 gvars
= env
.Dictionary()
560 newcom
= scons_subst("test $PartialCallable $SOURCES $TARGETS", env
,
561 target
=self
.MyNode('t'), source
=self
.MyNode('s'),
563 assert newcom
== "test CallableWithDefault: partial s t", newcom
565 def test_subst_partial_callable_with_no_default_expansion(self
):
566 """Test scons_subst(): expanding a functools.partial callable which sets
567 the value for extraneous function argument"""
568 env
= DummyEnv(self
.loc
)
569 gvars
= env
.Dictionary()
570 newcom
= scons_subst("test $PartialCallableNoDefault $SOURCES $TARGETS", env
,
571 target
=self
.MyNode('t'), source
=self
.MyNode('s'),
573 assert newcom
== "test CallableWithNoDefault: partialNoDefault s t", newcom
575 def test_subst_attribute_errors(self
):
576 """Test scons_subst(): handling attribute errors"""
577 env
= DummyEnv(self
.loc
)
581 scons_subst('${foo.bar}', env
, gvars
={'foo':Foo()})
582 except SCons
.Errors
.UserError
as e
:
584 "AttributeError `bar' trying to evaluate `${foo.bar}'",
585 "AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
586 "AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
587 "AttributeError `'Foo' object has no attribute 'bar'' trying to evaluate `${foo.bar}'",
589 assert str(e
) in expect
, e
591 raise AssertionError("did not catch expected UserError")
593 def test_subst_syntax_errors(self
):
594 """Test scons_subst(): handling syntax errors"""
595 env
= DummyEnv(self
.loc
)
597 scons_subst('$foo.bar.3.0', env
)
598 except SCons
.Errors
.UserError
as e
:
601 "SyntaxError `invalid syntax (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
602 # Python 3.10 and later
603 "SyntaxError `invalid syntax. Perhaps you forgot a comma? (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
605 assert str(e
) in expect
, e
607 raise AssertionError("did not catch expected UserError")
609 def test_subst_balance_errors(self
):
610 """Test scons_subst(): handling syntax errors"""
611 env
= DummyEnv(self
.loc
)
613 scons_subst('$(', env
, mode
=SUBST_SIG
)
614 except SCons
.Errors
.UserError
as e
:
615 assert str(e
) == "Unbalanced $(/$) in: $(", str(e
)
617 raise AssertionError("did not catch expected UserError")
620 scons_subst('$)', env
, mode
=SUBST_SIG
)
621 except SCons
.Errors
.UserError
as e
:
622 assert str(e
) == "Unbalanced $(/$) in: $)", str(e
)
624 raise AssertionError("did not catch expected UserError")
626 def test_subst_type_errors(self
):
627 """Test scons_subst(): handling type errors"""
628 env
= DummyEnv(self
.loc
)
630 scons_subst("${NONE[2]}", env
, gvars
={'NONE':None})
631 except SCons
.Errors
.UserError
as e
:
633 # Python 2.7 and later
634 "TypeError `'NoneType' object is not subscriptable' trying to evaluate `${NONE[2]}'",
635 # Python 2.7 and later under Fedora
636 "TypeError `'NoneType' object has no attribute '__getitem__'' trying to evaluate `${NONE[2]}'",
638 assert str(e
) in expect
, e
640 raise AssertionError("did not catch expected UserError")
645 scons_subst("${func(1)}", env
, gvars
={'func':func
})
646 except SCons
.Errors
.UserError
as e
:
648 # Python 3.5 (and 3.x?)
649 "TypeError `func() missing 2 required positional arguments: 'b' and 'c'' trying to evaluate `${func(1)}'",
651 "TypeError `scons_subst_TestCase.test_subst_type_errors.<locals>.func() missing 2 required positional arguments: 'b' and 'c'' trying to evaluate `${func(1)}'",
653 assert str(e
) in expect
, repr(str(e
))
655 raise AssertionError("did not catch expected UserError")
657 def test_subst_raw_function(self
):
658 """Test scons_subst(): fetch function with SUBST_RAW plus conv"""
659 # Test that the combination of SUBST_RAW plus a pass-through
660 # conversion routine allows us to fetch a function through the
661 # dictionary. CommandAction uses this to allow delayed evaluation
662 # of $SPAWN variables.
663 env
= DummyEnv(self
.loc
)
664 gvars
= env
.Dictionary()
666 r
= scons_subst("$CALLABLE1", env
, mode
=SUBST_RAW
, conv
=x
, gvars
=gvars
)
667 assert r
is self
.callable_object_1
, repr(r
)
668 r
= scons_subst("$CALLABLE1", env
, mode
=SUBST_RAW
, gvars
=gvars
)
669 assert r
== 'callable-1', repr(r
)
671 # Test how we handle overriding the internal conversion routines.
675 n1
= self
.MyNode('n1')
676 env
= DummyEnv({'NODE' : n1
})
677 gvars
= env
.Dictionary()
678 node
= scons_subst("$NODE", env
, mode
=SUBST_RAW
, conv
=s
, gvars
=gvars
)
679 assert node
is n1
, node
680 node
= scons_subst("$NODE", env
, mode
=SUBST_CMD
, conv
=s
, gvars
=gvars
)
681 assert node
is n1
, node
682 node
= scons_subst("$NODE", env
, mode
=SUBST_SIG
, conv
=s
, gvars
=gvars
)
683 assert node
is n1
, node
685 def test_subst_overriding_gvars(self
):
686 """Test scons_subst(): supplying an overriding gvars dictionary"""
687 env
= DummyEnv({'XXX' : 'xxx'})
688 result
= scons_subst('$XXX', env
, gvars
=env
.Dictionary())
689 assert result
== 'xxx', result
690 result
= scons_subst('$XXX', env
, gvars
={'XXX' : 'yyy'})
691 assert result
== 'yyy', result
693 class CLVar_TestCase(unittest
.TestCase
):
694 def test_CLVar(self
):
695 """Test scons_subst() and scons_subst_list() with CLVar objects"""
699 loc
['BAR'] = SCons
.Util
.CLVar('bar')
700 loc
['CALL'] = lambda target
, source
, env
, for_signature
: 'call'
703 cmd
= SCons
.Util
.CLVar("test $FOO $BAR $CALL test")
705 newcmd
= scons_subst(cmd
, env
, gvars
=env
.Dictionary())
706 assert newcmd
== ['test', 'foo', 'bar', 'call', 'test'], newcmd
708 cmd_list
= scons_subst_list(cmd
, env
, gvars
=env
.Dictionary())
709 assert len(cmd_list
) == 1, cmd_list
710 assert cmd_list
[0][0] == "test", cmd_list
[0][0]
711 assert cmd_list
[0][1] == "foo", cmd_list
[0][1]
712 assert cmd_list
[0][2] == "bar", cmd_list
[0][2]
713 assert cmd_list
[0][3] == "call", cmd_list
[0][3]
714 assert cmd_list
[0][4] == "test", cmd_list
[0][4]
716 class scons_subst_list_TestCase(SubstTestCase
):
721 ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
724 "$SOURCES $NEWLINE $TARGETS",
726 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
727 ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
732 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
746 "test ${SOURCES.attribute.attr1}",
748 ["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"],
751 "test ${SOURCES.attribute.attr2}",
753 ["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"],
756 "$DO --in=$FOO --out=$BAR",
758 ["do something", "--in=foo.in", "--out=bar with spaces.out"],
761 # This test is now fixed, and works like it should.
762 "$DO --in=$CRAZY --out=$BAR",
764 ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
767 # Try passing a list to scons_subst_list().
768 [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
770 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
771 ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
774 # Test against a former bug in scons_subst_list().
780 # Test double-dollar-sign behavior.
786 # Test various combinations of strings, lists and functions.
795 ['x', 'y'], [['x', 'y']],
805 '$S z', [['x', 'y', 'z']],
806 ['$S'], [['x', 'y']],
807 ['$S z'], [['x', 'y z']], # XXX - IS THIS BEST?
808 ['$S', 'z'], [['x', 'y', 'z']],
810 '$LS z', [['x y', 'z']],
812 ['$LS z'], [['x y z']],
813 ['$LS', 'z'], [['x y', 'z']],
815 '$L z', [['x', 'y', 'z']],
816 ['$L'], [['x', 'y']],
817 ['$L z'], [['x', 'y z']], # XXX - IS THIS BEST?
818 ['$L', 'z'], [['x', 'y', 'z']],
828 # Various uses of UserString.
829 collections
.UserString('x'), [['x']],
830 [collections
.UserString('x')], [['x']],
831 collections
.UserString('$X'), [['x']],
832 [collections
.UserString('$X')], [['x']],
833 collections
.UserString('$US'), [['us']],
834 [collections
.UserString('$US')], [['us']],
838 # Test function calls within ${}.
839 '$FUNCCALL', [['a', 'xc', 'b']],
841 # Test handling of newlines in white space.
842 'foo\nbar', [['foo'], ['bar']],
843 'foo\n\nbar', [['foo'], ['bar']],
844 'foo \n \n bar', [['foo'], ['bar']],
845 'foo \nmiddle\n bar', [['foo'], ['middle'], ['bar']],
847 # Bug reported by Christoph Wiedemann.
848 cvt('$xxx/bin'), [['/bin']],
850 # Test variables smooshed together with different prefixes.
851 'foo$AAA', [['fooa']],
852 '<$AAA', [['<', 'a']],
853 '>$AAA', [['>', 'a']],
854 '|$AAA', [['|', 'a']],
856 # Test callables that don't match our calling arguments.
857 '$CALLABLE2', [['callable-2']],
859 # Test handling of quotes.
860 # XXX Find a way to handle this in the future.
861 #'aaa "bbb ccc" ddd', [['aaa', 'bbb ccc', 'ddd']],
863 '${_defines(DEFS)}', [['Q1="q1"', 'Q2="a"']],
866 def test_scons_subst_list(self
):
867 """Test scons_subst_list(): basic substitution"""
868 def convert_lists(expect
):
869 return [list(map(cvt
, l
)) for l
in expect
]
870 return self
.basic_comparisons(scons_subst_list
, convert_lists
)
884 [["test", "$(", "$)"]],
888 "$AAA ${AAA}A $BBBB $BBB",
903 # Verify what happens with no target or source nodes.
914 # Various test refactored from ActionTests.py
916 [['This', 'is', '$(', '$)', 'test']],
917 [['This', 'is', 'test']],
918 [['This', 'is', 'test']],
920 ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
921 [["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]],
922 [["|", "a", "|", "b", "|", "c", "1"]],
923 [["|", "|", "c", "1"]],
926 def test_subst_env(self
):
927 """Test scons_subst_list(): expansion dictionary"""
928 # The expansion dictionary no longer comes from the construction
929 # environment automatically.
931 s
= scons_subst_list('$AAA', env
)
934 def test_subst_target_source(self
):
935 """Test scons_subst_list(): target= and source= arguments"""
936 env
= DummyEnv(self
.loc
)
937 gvars
= env
.Dictionary()
938 t1
= self
.MyNode('t1')
939 t2
= self
.MyNode('t2')
940 s1
= self
.MyNode('s1')
941 s2
= self
.MyNode('s2')
942 result
= scons_subst_list("$TARGET $SOURCES", env
,
946 assert result
== [['t1', 's1', 's2']], result
947 result
= scons_subst_list("$TARGET $SOURCES", env
,
951 assert result
== [['t1', 's1', 's2']], result
953 # Test interpolating a callable.
956 cmd_list
= scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES",
957 env
, target
=_t
, source
=_s
,
959 assert cmd_list
== [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list
961 def test_subst_escape(self
):
962 """Test scons_subst_list(): escape functionality"""
963 env
= DummyEnv(self
.loc
)
964 gvars
= env
.Dictionary()
965 def escape_func(foo
):
966 return '**' + foo
+ '**'
967 cmd_list
= scons_subst_list("abc $LITERALS xyz", env
, gvars
=gvars
)
968 assert cmd_list
== [['abc',
969 'foo\nwith\nnewlines',
970 'bar\nwith\nnewlines',
972 c
= cmd_list
[0][0].escape(escape_func
)
974 c
= cmd_list
[0][1].escape(escape_func
)
975 assert c
== '**foo\nwith\nnewlines**', c
976 c
= cmd_list
[0][2].escape(escape_func
)
977 assert c
== '**bar\nwith\nnewlines**', c
978 c
= cmd_list
[0][3].escape(escape_func
)
981 # We used to treat literals smooshed together like the whole
982 # thing was literal and escape it as a unit. The commented-out
983 # asserts below are in case we ever have to find a way to
984 # resurrect that functionality in some way.
985 cmd_list
= scons_subst_list("abc${LITERALS}xyz", env
, gvars
=gvars
)
986 c
= cmd_list
[0][0].escape(escape_func
)
987 #assert c == '**abcfoo\nwith\nnewlines**', c
988 assert c
== 'abcfoo\nwith\nnewlines', c
989 c
= cmd_list
[0][1].escape(escape_func
)
990 #assert c == '**bar\nwith\nnewlinesxyz**', c
991 assert c
== 'bar\nwith\nnewlinesxyz', c
995 cmd_list
= scons_subst_list('echo "target: $TARGET"', env
,
996 target
=_t
, gvars
=gvars
)
997 c
= cmd_list
[0][0].escape(escape_func
)
998 assert c
== 'echo', c
999 c
= cmd_list
[0][1].escape(escape_func
)
1000 assert c
== '"target:', c
1001 c
= cmd_list
[0][2].escape(escape_func
)
1004 def test_subst_SUBST_modes(self
):
1005 """Test scons_subst_list(): SUBST_* modes"""
1006 env
= DummyEnv(self
.loc
)
1007 subst_list_cases
= self
.subst_list_cases
[:]
1008 gvars
= env
.Dictionary()
1010 r
= scons_subst_list("$TARGET $SOURCES", env
, mode
=SUBST_RAW
, gvars
=gvars
)
1014 while subst_list_cases
:
1015 input, eraw
, ecmd
, esig
= subst_list_cases
[:4]
1016 result
= scons_subst_list(input, env
, mode
=SUBST_RAW
, gvars
=gvars
)
1018 if failed
== 0: print()
1019 print(" input %s => RAW %s did not match %s" % (repr(input), repr(result
), repr(eraw
)))
1021 result
= scons_subst_list(input, env
, mode
=SUBST_CMD
, gvars
=gvars
)
1023 if failed
== 0: print()
1024 print(" input %s => CMD %s did not match %s" % (repr(input), repr(result
), repr(ecmd
)))
1026 result
= scons_subst_list(input, env
, mode
=SUBST_SIG
, gvars
=gvars
)
1028 if failed
== 0: print()
1029 print(" input %s => SIG %s did not match %s" % (repr(input), repr(result
), repr(esig
)))
1031 del subst_list_cases
[:4]
1032 assert failed
== 0, "%d subst() mode cases failed" % failed
1034 def test_subst_attribute_errors(self
):
1035 """Test scons_subst_list(): handling attribute errors"""
1040 scons_subst_list('${foo.bar}', env
, gvars
={'foo':Foo()})
1041 except SCons
.Errors
.UserError
as e
:
1043 "AttributeError `bar' trying to evaluate `${foo.bar}'",
1044 "AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
1045 "AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
1046 "AttributeError `'Foo' object has no attribute 'bar'' trying to evaluate `${foo.bar}'",
1048 assert str(e
) in expect
, e
1050 raise AssertionError("did not catch expected UserError")
1052 def test_subst_syntax_errors(self
):
1053 """Test scons_subst_list(): handling syntax errors"""
1056 scons_subst_list('$foo.bar.3.0', env
)
1057 except SCons
.Errors
.UserError
as e
:
1060 "SyntaxError `invalid syntax (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
1061 # Python 3.10 and later
1062 "SyntaxError `invalid syntax. Perhaps you forgot a comma? (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
1064 assert str(e
) in expect
, e
1066 raise AssertionError("did not catch expected SyntaxError")
1068 def test_subst_raw_function(self
):
1069 """Test scons_subst_list(): fetch function with SUBST_RAW plus conv"""
1070 # Test that the combination of SUBST_RAW plus a pass-through
1071 # conversion routine allows us to fetch a function through the
1073 env
= DummyEnv(self
.loc
)
1074 gvars
= env
.Dictionary()
1076 r
= scons_subst_list("$CALLABLE2", env
, mode
=SUBST_RAW
, conv
=x
, gvars
=gvars
)
1077 assert r
== [[self
.callable_object_2
]], repr(r
)
1078 r
= scons_subst_list("$CALLABLE2", env
, mode
=SUBST_RAW
, gvars
=gvars
)
1079 assert r
== [['callable-2']], repr(r
)
1081 def test_subst_list_overriding_gvars(self
):
1082 """Test scons_subst_list(): overriding conv()"""
1087 n1
= self
.MyNode('n1')
1088 env
= DummyEnv({'NODE' : n1
})
1089 gvars
=env
.Dictionary()
1090 node
= scons_subst_list("$NODE", env
, mode
=SUBST_RAW
, conv
=s
, gvars
=gvars
)
1091 assert node
== [[n1
]], node
1092 node
= scons_subst_list("$NODE", env
, mode
=SUBST_CMD
, conv
=s
, gvars
=gvars
)
1093 assert node
== [[n1
]], node
1094 node
= scons_subst_list("$NODE", env
, mode
=SUBST_SIG
, conv
=s
, gvars
=gvars
)
1095 assert node
== [[n1
]], node
1097 def test_subst_list_overriding_gvars2(self
):
1098 """Test scons_subst_list(): supplying an overriding gvars dictionary"""
1099 env
= DummyEnv({'XXX' : 'xxx'})
1100 result
= scons_subst_list('$XXX', env
, gvars
=env
.Dictionary())
1101 assert result
== [['xxx']], result
1102 result
= scons_subst_list('$XXX', env
, gvars
={'XXX' : 'yyy'})
1103 assert result
== [['yyy']], result
1105 class scons_subst_once_TestCase(unittest
.TestCase
):
1108 'CCFLAGS' : '-DFOO',
1110 'RECURSE' : 'r $RECURSE r',
1111 'LIST' : ['a', 'b', 'c'],
1139 ['x', '$LIST', 'y'],
1141 ['x', 'a', 'b', 'c', 'y'],
1143 ['x', 'x $LIST y', 'y'],
1145 ['x', 'x a b c y', 'y'],
1147 ['x', 'x $CCFLAGS y', 'y'],
1149 ['x', 'x $CCFLAGS y', 'y'],
1151 ['x', 'x $RECURSE y', 'y'],
1153 ['x', 'x $RECURSE y', 'y'],
1156 def test_subst_once(self
):
1157 """Test the scons_subst_once() function"""
1158 env
= DummyEnv(self
.loc
)
1159 cases
= self
.basic_cases
[:]
1163 input, key
, expect
= cases
[:3]
1164 result
= scons_subst_once(input, env
, key
)
1165 if result
!= expect
:
1166 if failed
== 0: print()
1167 print(" input %s (%s) => %s did not match %s" % (repr(input), repr(key
), repr(result
), repr(expect
)))
1170 assert failed
== 0, "%d subst() cases failed" % failed
1172 class quote_spaces_TestCase(unittest
.TestCase
):
1173 def test_quote_spaces(self
):
1174 """Test the quote_spaces() method..."""
1175 q
= quote_spaces('x')
1178 q
= quote_spaces('x x')
1179 assert q
== '"x x"', q
1181 q
= quote_spaces('x\tx')
1182 assert q
== '"x\tx"', q
1185 def __init__(self
, name
, children
=[]):
1186 self
.children
= children
1194 def has_builder(self
):
1196 def has_explicit_builder(self
):
1198 def side_effect(self
):
1202 def always_build(self
):
1207 class LiteralTestCase(unittest
.TestCase
):
1208 def test_Literal(self
):
1209 """Test the Literal() function."""
1210 input_list
= [ '$FOO', Literal('$BAR') ]
1211 gvars
= { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
1213 def escape_func(cmd
):
1214 return '**' + cmd
+ '**'
1216 cmd_list
= scons_subst_list(input_list
, None, gvars
=gvars
)
1217 cmd_list
= escape_list(cmd_list
[0], escape_func
)
1218 assert cmd_list
== ['BAZ', '**$BAR**'], cmd_list
1220 def test_LiteralEqualsTest(self
):
1221 """Test that Literals compare for equality properly"""
1222 assert Literal('a literal') == Literal('a literal')
1223 assert Literal('a literal') != Literal('b literal')
1225 class SpecialAttrWrapperTestCase(unittest
.TestCase
):
1226 def test_SpecialAttrWrapper(self
):
1227 """Test the SpecialAttrWrapper() function."""
1228 input_list
= [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
1229 gvars
= { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
1231 def escape_func(cmd
):
1232 return '**' + cmd
+ '**'
1234 cmd_list
= scons_subst_list(input_list
, None, gvars
=gvars
)
1235 cmd_list
= escape_list(cmd_list
[0], escape_func
)
1236 assert cmd_list
== ['BAZ', '**$BAR**'], cmd_list
1238 cmd_list
= scons_subst_list(input_list
, None, mode
=SUBST_SIG
, gvars
=gvars
)
1239 cmd_list
= escape_list(cmd_list
[0], escape_func
)
1240 assert cmd_list
== ['BAZ', '**BLEH**'], cmd_list
1242 class subst_dict_TestCase(unittest
.TestCase
):
1243 def test_subst_dict(self
):
1244 """Test substituting dictionary values in an Action
1248 d
= subst_dict(target
=t
, source
=s
)
1249 assert str(d
['TARGETS'][0]) == 't', d
['TARGETS']
1250 assert str(d
['TARGET']) == 't', d
['TARGET']
1251 assert str(d
['SOURCES'][0]) == 's', d
['SOURCES']
1252 assert str(d
['SOURCE']) == 's', d
['SOURCE']
1254 t1
= DummyNode('t1')
1255 t2
= DummyNode('t2')
1256 s1
= DummyNode('s1')
1257 s2
= DummyNode('s2')
1258 d
= subst_dict(target
=[t1
, t2
], source
=[s1
, s2
])
1259 TARGETS
= sorted([str(x
) for x
in d
['TARGETS']])
1260 assert TARGETS
== ['t1', 't2'], d
['TARGETS']
1261 assert str(d
['TARGET']) == 't1', d
['TARGET']
1262 SOURCES
= sorted([str(x
) for x
in d
['SOURCES']])
1263 assert SOURCES
== ['s1', 's2'], d
['SOURCES']
1264 assert str(d
['SOURCE']) == 's1', d
['SOURCE']
1267 # Fake Value node with no rfile() method.
1268 def __init__(self
, name
):
1271 return 'v-'+self
.name
1272 def get_subst_proxy(self
):
1277 return self
.__class
__('rstr-' + self
.name
)
1280 t4
= DummyNode('t4')
1282 s3
= DummyNode('s3')
1285 d
= subst_dict(target
=[t3
, t4
, t5
], source
=[s3
, s4
, s5
])
1286 TARGETS
= sorted([str(x
) for x
in d
['TARGETS']])
1287 assert TARGETS
== ['t4', 'v-t3', 'v-t5'], TARGETS
1288 SOURCES
= sorted([str(x
) for x
in d
['SOURCES']])
1289 assert SOURCES
== ['s3', 'v-rstr-s4', 'v-s5'], SOURCES
1291 if __name__
== "__main__":
1296 # indent-tabs-mode:nil
1298 # vim: set expandtab tabstop=4 shiftwidth=4: