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
) -> None:
40 self
.name
= os
.path
.normpath(name
)
41 def __str__(self
) -> str:
43 def is_literal(self
) -> bool:
47 def get_subst_proxy(self
):
51 def __init__(self
, dict={}) -> None:
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) -> str:
74 def cl(target
=None, source
=None, env
=None, for_signature
=None):
77 def CmdGen1(target
, source
, env
, for_signature
) -> str:
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
) -> None:
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
: str="default") -> str:
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
) -> str:
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
) -> None:
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
) -> None:
136 self
.literal
= literal
137 def __str__(self
) -> str:
139 def is_literal(self
) -> bool:
143 def __init__(self
, value
) -> None:
145 def __call__(self
) -> None:
147 def __str__(self
) -> str:
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
) -> None:
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
) -> None:
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
) -> None:
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
) -> None:
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
) -> None:
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
) -> None:
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
) -> None:
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
) -> None:
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")
643 def func(a
, b
, c
) -> None:
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
) -> None:
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
) -> None:
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
) -> None:
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]
717 def test_subst_overriding_lvars_overrides(self
) -> None:
718 """Test that optional passed arg overrides overrides gvars, and existing lvars."""
719 env
=DummyEnv({'XXX' : 'xxx'})
720 result
= scons_subst('$XXX', env
, gvars
=env
.Dictionary(), overrides
={'XXX': 'yyz'})
721 assert result
== 'yyz', result
724 class scons_subst_list_TestCase(SubstTestCase
):
729 ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
732 "$SOURCES $NEWLINE $TARGETS",
734 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
735 ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
740 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
754 "test ${SOURCES.attribute.attr1}",
756 ["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"],
759 "test ${SOURCES.attribute.attr2}",
761 ["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"],
764 "$DO --in=$FOO --out=$BAR",
766 ["do something", "--in=foo.in", "--out=bar with spaces.out"],
769 # This test is now fixed, and works like it should.
770 "$DO --in=$CRAZY --out=$BAR",
772 ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
775 # Try passing a list to scons_subst_list().
776 [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
778 ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
779 ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
782 # Test against a former bug in scons_subst_list().
788 # Test double-dollar-sign behavior.
794 # Test various combinations of strings, lists and functions.
803 ['x', 'y'], [['x', 'y']],
813 '$S z', [['x', 'y', 'z']],
814 ['$S'], [['x', 'y']],
815 ['$S z'], [['x', 'y z']], # XXX - IS THIS BEST?
816 ['$S', 'z'], [['x', 'y', 'z']],
818 '$LS z', [['x y', 'z']],
820 ['$LS z'], [['x y z']],
821 ['$LS', 'z'], [['x y', 'z']],
823 '$L z', [['x', 'y', 'z']],
824 ['$L'], [['x', 'y']],
825 ['$L z'], [['x', 'y z']], # XXX - IS THIS BEST?
826 ['$L', 'z'], [['x', 'y', 'z']],
836 # Various uses of UserString.
837 collections
.UserString('x'), [['x']],
838 [collections
.UserString('x')], [['x']],
839 collections
.UserString('$X'), [['x']],
840 [collections
.UserString('$X')], [['x']],
841 collections
.UserString('$US'), [['us']],
842 [collections
.UserString('$US')], [['us']],
846 # Test function calls within ${}.
847 '$FUNCCALL', [['a', 'xc', 'b']],
849 # Test handling of newlines in white space.
850 'foo\nbar', [['foo'], ['bar']],
851 'foo\n\nbar', [['foo'], ['bar']],
852 'foo \n \n bar', [['foo'], ['bar']],
853 'foo \nmiddle\n bar', [['foo'], ['middle'], ['bar']],
855 # Bug reported by Christoph Wiedemann.
856 cvt('$xxx/bin'), [['/bin']],
858 # Test variables smooshed together with different prefixes.
859 'foo$AAA', [['fooa']],
860 '<$AAA', [['<', 'a']],
861 '>$AAA', [['>', 'a']],
862 '|$AAA', [['|', 'a']],
864 # Test callables that don't match our calling arguments.
865 '$CALLABLE2', [['callable-2']],
867 # Test handling of quotes.
868 # XXX Find a way to handle this in the future.
869 #'aaa "bbb ccc" ddd', [['aaa', 'bbb ccc', 'ddd']],
871 '${_defines(DEFS)}', [['Q1="q1"', 'Q2="a"']],
874 def test_scons_subst_list(self
):
875 """Test scons_subst_list(): basic substitution"""
876 def convert_lists(expect
):
877 return [list(map(cvt
, l
)) for l
in expect
]
878 return self
.basic_comparisons(scons_subst_list
, convert_lists
)
892 [["test", "$(", "$)"]],
896 "$AAA ${AAA}A $BBBB $BBB",
911 # Verify what happens with no target or source nodes.
922 # Various test refactored from ActionTests.py
924 [['This', 'is', '$(', '$)', 'test']],
925 [['This', 'is', 'test']],
926 [['This', 'is', 'test']],
928 ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
929 [["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]],
930 [["|", "a", "|", "b", "|", "c", "1"]],
931 [["|", "|", "c", "1"]],
934 def test_subst_env(self
) -> None:
935 """Test scons_subst_list(): expansion dictionary"""
936 # The expansion dictionary no longer comes from the construction
937 # environment automatically.
939 s
= scons_subst_list('$AAA', env
)
942 def test_subst_target_source(self
) -> None:
943 """Test scons_subst_list(): target= and source= arguments"""
944 env
= DummyEnv(self
.loc
)
945 gvars
= env
.Dictionary()
946 t1
= self
.MyNode('t1')
947 t2
= self
.MyNode('t2')
948 s1
= self
.MyNode('s1')
949 s2
= self
.MyNode('s2')
950 result
= scons_subst_list("$TARGET $SOURCES", env
,
954 assert result
== [['t1', 's1', 's2']], result
955 result
= scons_subst_list("$TARGET $SOURCES", env
,
959 assert result
== [['t1', 's1', 's2']], result
961 # Test interpolating a callable.
964 cmd_list
= scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES",
965 env
, target
=_t
, source
=_s
,
967 assert cmd_list
== [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list
969 def test_subst_escape(self
) -> None:
970 """Test scons_subst_list(): escape functionality"""
971 env
= DummyEnv(self
.loc
)
972 gvars
= env
.Dictionary()
973 def escape_func(foo
):
974 return '**' + foo
+ '**'
975 cmd_list
= scons_subst_list("abc $LITERALS xyz", env
, gvars
=gvars
)
976 assert cmd_list
== [['abc',
977 'foo\nwith\nnewlines',
978 'bar\nwith\nnewlines',
980 c
= cmd_list
[0][0].escape(escape_func
)
982 c
= cmd_list
[0][1].escape(escape_func
)
983 assert c
== '**foo\nwith\nnewlines**', c
984 c
= cmd_list
[0][2].escape(escape_func
)
985 assert c
== '**bar\nwith\nnewlines**', c
986 c
= cmd_list
[0][3].escape(escape_func
)
989 # We used to treat literals smooshed together like the whole
990 # thing was literal and escape it as a unit. The commented-out
991 # asserts below are in case we ever have to find a way to
992 # resurrect that functionality in some way.
993 cmd_list
= scons_subst_list("abc${LITERALS}xyz", env
, gvars
=gvars
)
994 c
= cmd_list
[0][0].escape(escape_func
)
995 #assert c == '**abcfoo\nwith\nnewlines**', c
996 assert c
== 'abcfoo\nwith\nnewlines', c
997 c
= cmd_list
[0][1].escape(escape_func
)
998 #assert c == '**bar\nwith\nnewlinesxyz**', c
999 assert c
== 'bar\nwith\nnewlinesxyz', c
1003 cmd_list
= scons_subst_list('echo "target: $TARGET"', env
,
1004 target
=_t
, gvars
=gvars
)
1005 c
= cmd_list
[0][0].escape(escape_func
)
1006 assert c
== 'echo', c
1007 c
= cmd_list
[0][1].escape(escape_func
)
1008 assert c
== '"target:', c
1009 c
= cmd_list
[0][2].escape(escape_func
)
1012 def test_subst_SUBST_modes(self
) -> None:
1013 """Test scons_subst_list(): SUBST_* modes"""
1014 env
= DummyEnv(self
.loc
)
1015 subst_list_cases
= self
.subst_list_cases
[:]
1016 gvars
= env
.Dictionary()
1018 r
= scons_subst_list("$TARGET $SOURCES", env
, mode
=SUBST_RAW
, gvars
=gvars
)
1022 while subst_list_cases
:
1023 input, eraw
, ecmd
, esig
= subst_list_cases
[:4]
1024 result
= scons_subst_list(input, env
, mode
=SUBST_RAW
, gvars
=gvars
)
1026 if failed
== 0: print()
1027 print(" input %s => RAW %s did not match %s" % (repr(input), repr(result
), repr(eraw
)))
1029 result
= scons_subst_list(input, env
, mode
=SUBST_CMD
, gvars
=gvars
)
1031 if failed
== 0: print()
1032 print(" input %s => CMD %s did not match %s" % (repr(input), repr(result
), repr(ecmd
)))
1034 result
= scons_subst_list(input, env
, mode
=SUBST_SIG
, gvars
=gvars
)
1036 if failed
== 0: print()
1037 print(" input %s => SIG %s did not match %s" % (repr(input), repr(result
), repr(esig
)))
1039 del subst_list_cases
[:4]
1040 assert failed
== 0, "%d subst() mode cases failed" % failed
1042 def test_subst_attribute_errors(self
):
1043 """Test scons_subst_list(): handling attribute errors"""
1048 scons_subst_list('${foo.bar}', env
, gvars
={'foo':Foo()})
1049 except SCons
.Errors
.UserError
as e
:
1051 "AttributeError `bar' trying to evaluate `${foo.bar}'",
1052 "AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
1053 "AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
1054 "AttributeError `'Foo' object has no attribute 'bar'' trying to evaluate `${foo.bar}'",
1056 assert str(e
) in expect
, e
1058 raise AssertionError("did not catch expected UserError")
1060 def test_subst_syntax_errors(self
):
1061 """Test scons_subst_list(): handling syntax errors"""
1064 scons_subst_list('$foo.bar.3.0', env
)
1065 except SCons
.Errors
.UserError
as e
:
1068 "SyntaxError `invalid syntax (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
1069 # Python 3.10 and later
1070 "SyntaxError `invalid syntax. Perhaps you forgot a comma? (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
1072 assert str(e
) in expect
, e
1074 raise AssertionError("did not catch expected SyntaxError")
1076 def test_subst_raw_function(self
) -> None:
1077 """Test scons_subst_list(): fetch function with SUBST_RAW plus conv"""
1078 # Test that the combination of SUBST_RAW plus a pass-through
1079 # conversion routine allows us to fetch a function through the
1081 env
= DummyEnv(self
.loc
)
1082 gvars
= env
.Dictionary()
1084 r
= scons_subst_list("$CALLABLE2", env
, mode
=SUBST_RAW
, conv
=x
, gvars
=gvars
)
1085 assert r
== [[self
.callable_object_2
]], repr(r
)
1086 r
= scons_subst_list("$CALLABLE2", env
, mode
=SUBST_RAW
, gvars
=gvars
)
1087 assert r
== [['callable-2']], repr(r
)
1089 def test_subst_list_overriding_gvars(self
) -> None:
1090 """Test scons_subst_list(): overriding conv()"""
1095 n1
= self
.MyNode('n1')
1096 env
= DummyEnv({'NODE' : n1
})
1097 gvars
=env
.Dictionary()
1098 node
= scons_subst_list("$NODE", env
, mode
=SUBST_RAW
, conv
=s
, gvars
=gvars
)
1099 assert node
== [[n1
]], node
1100 node
= scons_subst_list("$NODE", env
, mode
=SUBST_CMD
, conv
=s
, gvars
=gvars
)
1101 assert node
== [[n1
]], node
1102 node
= scons_subst_list("$NODE", env
, mode
=SUBST_SIG
, conv
=s
, gvars
=gvars
)
1103 assert node
== [[n1
]], node
1105 def test_subst_list_overriding_gvars2(self
) -> None:
1106 """Test scons_subst_list(): supplying an overriding gvars dictionary"""
1107 env
= DummyEnv({'XXX' : 'xxx'})
1108 result
= scons_subst_list('$XXX', env
, gvars
=env
.Dictionary())
1109 assert result
== [['xxx']], result
1110 result
= scons_subst_list('$XXX', env
, gvars
={'XXX' : 'yyy'})
1111 assert result
== [['yyy']], result
1113 def test_subst_list_overriding_lvars_overrides(self
) -> None:
1114 """Test that optional passed arg overrides overrides gvars, and existing lvars."""
1115 env
= DummyEnv({'XXX':'xxx'})
1116 result
= scons_subst_list('$XXX', env
, gvars
=env
.Dictionary(), overrides
={'XXX': 'yyy'})
1117 assert result
== [['yyy']], result
1120 class scons_subst_once_TestCase(unittest
.TestCase
):
1123 'CCFLAGS' : '-DFOO',
1125 'RECURSE' : 'r $RECURSE r',
1126 'LIST' : ['a', 'b', 'c'],
1154 ['x', '$LIST', 'y'],
1156 ['x', 'a', 'b', 'c', 'y'],
1158 ['x', 'x $LIST y', 'y'],
1160 ['x', 'x a b c y', 'y'],
1162 ['x', 'x $CCFLAGS y', 'y'],
1164 ['x', 'x $CCFLAGS y', 'y'],
1166 ['x', 'x $RECURSE y', 'y'],
1168 ['x', 'x $RECURSE y', 'y'],
1171 def test_subst_once(self
) -> None:
1172 """Test the scons_subst_once() function"""
1173 env
= DummyEnv(self
.loc
)
1174 cases
= self
.basic_cases
[:]
1178 input, key
, expect
= cases
[:3]
1179 result
= scons_subst_once(input, env
, key
)
1180 if result
!= expect
:
1181 if failed
== 0: print()
1182 print(" input %s (%s) => %s did not match %s" % (repr(input), repr(key
), repr(result
), repr(expect
)))
1185 assert failed
== 0, "%d subst() cases failed" % failed
1187 class quote_spaces_TestCase(unittest
.TestCase
):
1188 def test_quote_spaces(self
) -> None:
1189 """Test the quote_spaces() method..."""
1190 q
= quote_spaces('x')
1193 q
= quote_spaces('x x')
1194 assert q
== '"x x"', q
1196 q
= quote_spaces('x\tx')
1197 assert q
== '"x\tx"', q
1200 def __init__(self
, name
, children
=[]) -> None:
1201 self
.children
= children
1204 def __str__(self
) -> str:
1206 def exists(self
) -> bool:
1208 def rexists(self
) -> bool:
1210 def has_builder(self
) -> bool:
1212 def has_explicit_builder(self
) -> bool:
1214 def side_effect(self
) -> bool:
1216 def precious(self
) -> bool:
1218 def always_build(self
) -> bool:
1220 def current(self
) -> bool:
1223 class LiteralTestCase(unittest
.TestCase
):
1224 def test_Literal(self
) -> None:
1225 """Test the Literal() function."""
1226 input_list
= [ '$FOO', Literal('$BAR') ]
1227 gvars
= { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
1229 def escape_func(cmd
):
1230 return '**' + cmd
+ '**'
1232 cmd_list
= scons_subst_list(input_list
, None, gvars
=gvars
)
1233 cmd_list
= escape_list(cmd_list
[0], escape_func
)
1234 assert cmd_list
== ['BAZ', '**$BAR**'], cmd_list
1236 def test_LiteralEqualsTest(self
) -> None:
1237 """Test that Literals compare for equality properly"""
1238 assert Literal('a literal') == Literal('a literal')
1239 assert Literal('a literal') != Literal('b literal')
1241 class SpecialAttrWrapperTestCase(unittest
.TestCase
):
1242 def test_SpecialAttrWrapper(self
) -> None:
1243 """Test the SpecialAttrWrapper() function."""
1244 input_list
= [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
1245 gvars
= { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
1247 def escape_func(cmd
):
1248 return '**' + cmd
+ '**'
1250 cmd_list
= scons_subst_list(input_list
, None, gvars
=gvars
)
1251 cmd_list
= escape_list(cmd_list
[0], escape_func
)
1252 assert cmd_list
== ['BAZ', '**$BAR**'], cmd_list
1254 cmd_list
= scons_subst_list(input_list
, None, mode
=SUBST_SIG
, gvars
=gvars
)
1255 cmd_list
= escape_list(cmd_list
[0], escape_func
)
1256 assert cmd_list
== ['BAZ', '**BLEH**'], cmd_list
1258 class subst_dict_TestCase(unittest
.TestCase
):
1259 def test_subst_dict(self
) -> None:
1260 """Test substituting dictionary values in an Action
1264 d
= subst_dict(target
=t
, source
=s
)
1265 assert str(d
['TARGETS'][0]) == 't', d
['TARGETS']
1266 assert str(d
['TARGET']) == 't', d
['TARGET']
1267 assert str(d
['SOURCES'][0]) == 's', d
['SOURCES']
1268 assert str(d
['SOURCE']) == 's', d
['SOURCE']
1270 t1
= DummyNode('t1')
1271 t2
= DummyNode('t2')
1272 s1
= DummyNode('s1')
1273 s2
= DummyNode('s2')
1274 d
= subst_dict(target
=[t1
, t2
], source
=[s1
, s2
])
1275 TARGETS
= sorted([str(x
) for x
in d
['TARGETS']])
1276 assert TARGETS
== ['t1', 't2'], d
['TARGETS']
1277 assert str(d
['TARGET']) == 't1', d
['TARGET']
1278 SOURCES
= sorted([str(x
) for x
in d
['SOURCES']])
1279 assert SOURCES
== ['s1', 's2'], d
['SOURCES']
1280 assert str(d
['SOURCE']) == 's1', d
['SOURCE']
1283 # Fake Value node with no rfile() method.
1284 def __init__(self
, name
) -> None:
1286 def __str__(self
) -> str:
1287 return 'v-'+self
.name
1288 def get_subst_proxy(self
):
1293 return self
.__class
__('rstr-' + self
.name
)
1296 t4
= DummyNode('t4')
1298 s3
= DummyNode('s3')
1301 d
= subst_dict(target
=[t3
, t4
, t5
], source
=[s3
, s4
, s5
])
1302 TARGETS
= sorted([str(x
) for x
in d
['TARGETS']])
1303 assert TARGETS
== ['t4', 'v-t3', 'v-t5'], TARGETS
1304 SOURCES
= sorted([str(x
) for x
in d
['SOURCES']])
1305 assert SOURCES
== ['s3', 'v-rstr-s4', 'v-s5'], SOURCES
1307 if __name__
== "__main__":
1312 # indent-tabs-mode:nil
1314 # vim: set expandtab tabstop=4 shiftwidth=4: