pandemonium_engine/SCSCons/SubstTests.py

1299 lines
45 KiB
Python
Raw Normal View History

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import SCons.compat
import os
import unittest
from functools import partial
import SCons.Errors
from SCons.Subst import (Literal, SUBST_CMD, SUBST_RAW, SUBST_SIG, SpecialAttrWrapper, collections,
escape_list, quote_spaces, scons_subst, scons_subst_list, scons_subst_once,
subst_dict)
class DummyNode:
"""Simple node work-alike."""
def __init__(self, name):
self.name = os.path.normpath(name)
def __str__(self):
return self.name
def is_literal(self):
return 1
def rfile(self):
return self
def get_subst_proxy(self):
return self
class DummyEnv:
def __init__(self, dict={}):
self.dict = dict
def Dictionary(self, key = None):
if not key:
return self.dict
return self.dict[key]
def __getitem__(self, key):
return self.dict[key]
def get(self, key, default):
return self.dict.get(key, default)
def sig_dict(self):
dict = self.dict.copy()
dict["TARGETS"] = 'tsig'
dict["SOURCES"] = 'ssig'
return dict
def cs(target=None, source=None, env=None, for_signature=None):
return 'cs'
def cl(target=None, source=None, env=None, for_signature=None):
return ['cl']
def CmdGen1(target, source, env, for_signature):
# Nifty trick...since Environment references are interpolated,
# instantiate an instance of a callable class with this one,
# which will then get evaluated.
assert str(target) == 't', target
assert str(source) == 's', source
return "${CMDGEN2('foo', %d)}" % for_signature
class CmdGen2:
def __init__(self, mystr, forsig):
self.mystr = mystr
self.expect_for_signature = forsig
def __call__(self, target, source, env, for_signature):
assert str(target) == 't', target
assert str(source) == 's', source
assert for_signature == self.expect_for_signature, for_signature
return [ self.mystr, env.Dictionary('BAR') ]
def CallableWithDefault(target, source, env, for_signature, other_value="default"):
assert str(target) == 't', target
assert str(source) == 's', source
return "CallableWithDefault: %s"%other_value
PartialCallable = partial(CallableWithDefault, other_value="partial")
def CallableWithNoDefault(target, source, env, for_signature, other_value):
assert str(target) == 't', target
assert str(source) == 's', source
return "CallableWithNoDefault: %s"%other_value
PartialCallableNoDefault = partial(CallableWithNoDefault, other_value="partialNoDefault")
if os.sep == '/':
def cvt(str):
return str
else:
def cvt(str):
return str.replace('/', os.sep)
class SubstTestCase(unittest.TestCase):
class MyNode(DummyNode):
"""Simple node work-alike with some extra stuff for testing."""
def __init__(self, name):
DummyNode.__init__(self, name)
class Attribute:
pass
self.attribute = Attribute()
self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
def get_stuff(self, extra):
return self.name + extra
foo = 1
class TestLiteral:
def __init__(self, literal):
self.literal = literal
def __str__(self):
return self.literal
def is_literal(self):
return 1
class TestCallable:
def __init__(self, value):
self.value = value
def __call__(self):
pass
def __str__(self):
return self.value
# only use of this is currently commented out below
#def function_foo(arg):
# pass
target = [ MyNode("./foo/bar.exe"),
MyNode("/bar/baz with spaces.obj"),
MyNode("../foo/baz.obj") ]
source = [ MyNode("./foo/blah with spaces.cpp"),
MyNode("/bar/ack.cpp"),
MyNode("../foo/ack.c") ]
callable_object_1 = TestCallable('callable-1')
callable_object_2 = TestCallable('callable-2')
def _defines(defs):
l = []
for d in defs:
if SCons.Util.is_List(d) or isinstance(d, tuple):
l.append(str(d[0]) + '=' + str(d[1]))
else:
l.append(str(d))
return l
loc = {
'xxx' : None,
'NEWLINE' : 'before\nafter',
'null' : '',
'zero' : 0,
'one' : 1,
'BAZ' : 'baz',
'ONE' : '$TWO',
'TWO' : '$THREE',
'THREE' : 'four',
'AAA' : 'a',
'BBB' : 'b',
'CCC' : 'c',
'DO' : DummyNode('do something'),
'FOO' : DummyNode('foo.in'),
'BAR' : DummyNode('bar with spaces.out'),
'CRAZY' : DummyNode('crazy\nfile.in'),
# $XXX$HHH should expand to GGGIII, not BADNEWS.
'XXX' : '$FFF',
'FFF' : 'GGG',
'HHH' : 'III',
'FFFIII' : 'BADNEWS',
'THING1' : "$(STUFF$)",
'THING2' : "$THING1",
'LITERAL' : TestLiteral("$XXX"),
# Test that we can expand to and return a function.
#'FUNCTION' : function_foo,
'CMDGEN1' : CmdGen1,
'CMDGEN2' : CmdGen2,
'CallableWithDefault': CallableWithDefault,
'PartialCallable' : PartialCallable,
'PartialCallableNoDefault' : PartialCallableNoDefault,
'LITERALS' : [ Literal('foo\nwith\nnewlines'),
Literal('bar\nwith\nnewlines') ],
'NOTHING' : "",
'NONE' : None,
# Test various combinations of strings, lists and functions.
'N' : None,
'X' : 'x',
'Y' : '$X',
'R' : '$R',
'S' : 'x y',
'LS' : ['x y'],
'L' : ['x', 'y'],
'TS' : ('x y',),
'T' : ('x', 'y'),
'CS' : cs,
'CL' : cl,
'US' : collections.UserString('us'),
# Test function calls within ${}.
'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}',
'FUNC1' : lambda x: x,
'FUNC2' : lambda target, source, env, for_signature: ['x$CCC'],
# Various tests refactored from ActionTests.py.
'LIST' : [["This", "is", "$(", "$a", "$)", "test"]],
# Test recursion.
'RECURSE' : 'foo $RECURSE bar',
'RRR' : 'foo $SSS bar',
'SSS' : '$RRR',
# Test callables that don't match the calling arguments.
'CALLABLE1' : callable_object_1,
'CALLABLE2' : callable_object_2,
'_defines' : _defines,
'DEFS' : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ],
}
def basic_comparisons(self, function, convert):
env = DummyEnv(self.loc)
cases = self.basic_cases[:]
kwargs = {'target' : self.target, 'source' : self.source,
'gvars' : env.Dictionary()}
failed = 0
case_count = 0
while cases:
input, expect = cases[:2]
expect = convert(expect)
try:
result = function(input, env, **kwargs)
except Exception as e:
fmt = " input %s generated %s (%s)"
print(fmt % (repr(input), e.__class__.__name__, repr(e)))
failed = failed + 1
else:
if result != expect:
if failed == 0: print()
print("[%4d] input %s => \n%s did not match \n%s" % (case_count, repr(input), repr(result), repr(expect)))
failed = failed + 1
del cases[:2]
case_count += 1
fmt = "%d %s() cases failed"
assert failed == 0, fmt % (failed, function.__name__)
class scons_subst_TestCase(SubstTestCase):
# Basic tests of substitution functionality.
basic_cases = [
# Basics: strings without expansions are left alone, and
# the simplest possible expansion to a null-string value.
"test", "test",
"$null", "",
# Test expansion of integer values.
"test $zero", "test 0",
"test $one", "test 1",
# Test multiple re-expansion of values.
"test $ONE", "test four",
# Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions.
"test $TARGETS $SOURCES",
"test foo/bar.exe /bar/baz with spaces.obj ../foo/baz.obj foo/blah with spaces.cpp /bar/ack.cpp ../foo/ack.c",
"test ${TARGETS[:]} ${SOURCES[0]}",
"test foo/bar.exe /bar/baz with spaces.obj ../foo/baz.obj foo/blah with spaces.cpp",
"test ${TARGETS[1:]}v",
"test /bar/baz with spaces.obj ../foo/baz.objv",
"test $TARGET",
"test foo/bar.exe",
"test $TARGET$NO_SUCH_VAR[0]",
"test foo/bar.exe[0]",
"test $TARGETS.foo",
"test 1 1 1",
"test ${SOURCES[0:2].foo}",
"test 1 1",
"test $SOURCE.foo",
"test 1",
"test ${TARGET.get_stuff('blah')}",
"test foo/bar.exeblah",
"test ${SOURCES.get_stuff('blah')}",
"test foo/blah with spaces.cppblah /bar/ack.cppblah ../foo/ack.cblah",
"test ${SOURCES[0:2].get_stuff('blah')}",
"test foo/blah with spaces.cppblah /bar/ack.cppblah",
"test ${SOURCES[0:2].get_stuff('blah')}",
"test foo/blah with spaces.cppblah /bar/ack.cppblah",
"test ${SOURCES.attribute.attr1}",
"test attr$1-blah with spaces.cpp attr$1-ack.cpp attr$1-ack.c",
"test ${SOURCES.attribute.attr2}",
"test attr$2-blah with spaces.cpp attr$2-ack.cpp attr$2-ack.c",
# Test adjacent expansions.
"foo$BAZ",
"foobaz",
"foo${BAZ}",
"foobaz",
# Test that adjacent expansions don't get re-interpreted
# together. The correct disambiguated expansion should be:
# $XXX$HHH => ${FFF}III => GGGIII
# not:
# $XXX$HHH => ${FFFIII} => BADNEWS
"$XXX$HHH", "GGGIII",
# Test double-dollar-sign behavior.
"$$FFF$HHH", "$FFFIII",
# Test double-dollar-sign before open paren. It's not meant
# to be signature escaping
'echo $$(pwd) > XYZ', 'echo $(pwd) > XYZ',
# Test that a Literal will stop dollar-sign substitution.
"$XXX $LITERAL $FFF", "GGG $XXX GGG",
# Test that we don't blow up even if they subscript
# something in ways they "can't."
"${FFF[0]}", "G",
"${FFF[7]}", "",
"${NOTHING[1]}", "",
# Test various combinations of strings and lists.
#None, '',
'', '',
'x', 'x',
'x y', 'x y',
'$N', '',
'$X', 'x',
'$Y', 'x',
'$R', '',
'$S', 'x y',
'$LS', 'x y',
'$L', 'x y',
'$TS', 'x y',
'$T', 'x y',
'$S z', 'x y z',
'$LS z', 'x y z',
'$L z', 'x y z',
'$TS z', 'x y z',
'$T z', 'x y z',
#cs, 'cs',
#cl, 'cl',
'$CS', 'cs',
'$CL', 'cl',
# Various uses of UserString.
collections.UserString('x'), 'x',
collections.UserString('$X'), 'x',
collections.UserString('$US'), 'us',
'$US', 'us',
# Test function calls within ${}.
'$FUNCCALL', 'a xc b',
# Bug reported by Christoph Wiedemann.
cvt('$xxx/bin'), '/bin',
# Tests callables that don't match our calling arguments.
'$CALLABLE1', 'callable-1',
# Test handling of quotes.
'aaa "bbb ccc" ddd', 'aaa "bbb ccc" ddd',
]
def test_scons_subst(self):
"""Test scons_subst(): basic substitution"""
return self.basic_comparisons(scons_subst, cvt)
subst_cases = [
"test $xxx",
"test ",
"test",
"test",
"test $($xxx$)",
"test $($)",
"test",
"test",
"test $( $xxx $)",
"test $( $)",
"test",
"test",
"test $( $THING2 $)",
"test $( $(STUFF$) $)",
"test STUFF",
"test",
"$AAA ${AAA}A $BBBB $BBB",
"a aA b",
"a aA b",
"a aA b",
"$RECURSE",
"foo bar",
"foo bar",
"foo bar",
"$RRR",
"foo bar",
"foo bar",
"foo bar",
# Verify what happens with no target or source nodes.
"$TARGET $SOURCES",
" ",
"",
"",
"$TARGETS $SOURCE",
" ",
"",
"",
# Various tests refactored from ActionTests.py.
"${LIST}",
"This is $( $) test",
"This is test",
"This is test",
["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
["|", "$(", "a", "|", "b", "$)", "|", "c", "1"],
["|", "a", "|", "b", "|", "c", "1"],
["|", "|", "c", "1"],
]
def test_subst_env(self):
"""Test scons_subst(): expansion dictionary"""
# The expansion dictionary no longer comes from the construction
# environment automatically.
env = DummyEnv(self.loc)
s = scons_subst('$AAA', env)
assert s == '', s
def test_subst_SUBST_modes(self):
"""Test scons_subst(): SUBST_* modes"""
env = DummyEnv(self.loc)
subst_cases = self.subst_cases[:]
gvars = env.Dictionary()
failed = 0
while subst_cases:
input, eraw, ecmd, esig = subst_cases[:4]
result = scons_subst(input, env, mode=SUBST_RAW, gvars=gvars)
if result != eraw:
if failed == 0: print()
print(" input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw)))
failed = failed + 1
result = scons_subst(input, env, mode=SUBST_CMD, gvars=gvars)
if result != ecmd:
if failed == 0: print()
print(" input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd)))
failed = failed + 1
result = scons_subst(input, env, mode=SUBST_SIG, gvars=gvars)
if result != esig:
if failed == 0: print()
print(" input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig)))
failed = failed + 1
del subst_cases[:4]
assert failed == 0, "%d subst() mode cases failed" % failed
def test_subst_target_source(self):
"""Test scons_subst(): target= and source= arguments"""
env = DummyEnv(self.loc)
t1 = self.MyNode('t1')
t2 = self.MyNode('t2')
s1 = self.MyNode('s1')
s2 = self.MyNode('s2')
result = scons_subst("$TARGET $SOURCES", env,
target=[t1, t2],
source=[s1, s2])
assert result == "t1 s1 s2", result
result = scons_subst("$TARGET $SOURCES", env,
target=[t1, t2],
source=[s1, s2],
gvars={})
assert result == "t1 s1 s2", result
result = scons_subst("$TARGET $SOURCES", env, target=[], source=[])
assert result == " ", result
result = scons_subst("$TARGETS $SOURCE", env, target=[], source=[])
assert result == " ", result
def test_subst_callable_expansion(self):
"""Test scons_subst(): expanding a callable"""
env = DummyEnv(self.loc)
gvars = env.Dictionary()
newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS", env,
target=self.MyNode('t'), source=self.MyNode('s'),
gvars=gvars)
assert newcom == "test foo bar with spaces.out s t", newcom
def test_subst_callable_with_default_expansion(self):
"""Test scons_subst(): expanding a callable with a default value arg"""
env = DummyEnv(self.loc)
gvars = env.Dictionary()
newcom = scons_subst("test $CallableWithDefault $SOURCES $TARGETS", env,
target=self.MyNode('t'), source=self.MyNode('s'),
gvars=gvars)
assert newcom == "test CallableWithDefault: default s t", newcom
def test_subst_partial_callable_with_default_expansion(self):
"""Test scons_subst(): expanding a functools.partial callable which sets
the default value in the callable"""
env = DummyEnv(self.loc)
gvars = env.Dictionary()
newcom = scons_subst("test $PartialCallable $SOURCES $TARGETS", env,
target=self.MyNode('t'), source=self.MyNode('s'),
gvars=gvars)
assert newcom == "test CallableWithDefault: partial s t", newcom
def test_subst_partial_callable_with_no_default_expansion(self):
"""Test scons_subst(): expanding a functools.partial callable which sets
the value for extraneous function argument"""
env = DummyEnv(self.loc)
gvars = env.Dictionary()
newcom = scons_subst("test $PartialCallableNoDefault $SOURCES $TARGETS", env,
target=self.MyNode('t'), source=self.MyNode('s'),
gvars=gvars)
assert newcom == "test CallableWithNoDefault: partialNoDefault s t", newcom
def test_subst_attribute_errors(self):
"""Test scons_subst(): handling attribute errors"""
env = DummyEnv(self.loc)
try:
class Foo:
pass
scons_subst('${foo.bar}', env, gvars={'foo':Foo()})
except SCons.Errors.UserError as e:
expect = [
"AttributeError `bar' trying to evaluate `${foo.bar}'",
"AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
"AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
"AttributeError `'Foo' object has no attribute 'bar'' trying to evaluate `${foo.bar}'",
]
assert str(e) in expect, e
else:
raise AssertionError("did not catch expected UserError")
def test_subst_syntax_errors(self):
"""Test scons_subst(): handling syntax errors"""
env = DummyEnv(self.loc)
try:
scons_subst('$foo.bar.3.0', env)
except SCons.Errors.UserError as e:
expect = [
# Python 2.5 to 3.9
"SyntaxError `invalid syntax (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
# Python 3.10 and later
"SyntaxError `invalid syntax. Perhaps you forgot a comma? (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
]
assert str(e) in expect, e
else:
raise AssertionError("did not catch expected UserError")
def test_subst_balance_errors(self):
"""Test scons_subst(): handling syntax errors"""
env = DummyEnv(self.loc)
try:
scons_subst('$(', env, mode=SUBST_SIG)
except SCons.Errors.UserError as e:
assert str(e) == "Unbalanced $(/$) in: $(", str(e)
else:
raise AssertionError("did not catch expected UserError")
try:
scons_subst('$)', env, mode=SUBST_SIG)
except SCons.Errors.UserError as e:
assert str(e) == "Unbalanced $(/$) in: $)", str(e)
else:
raise AssertionError("did not catch expected UserError")
def test_subst_type_errors(self):
"""Test scons_subst(): handling type errors"""
env = DummyEnv(self.loc)
try:
scons_subst("${NONE[2]}", env, gvars={'NONE':None})
except SCons.Errors.UserError as e:
expect = [
# Python 2.7 and later
"TypeError `'NoneType' object is not subscriptable' trying to evaluate `${NONE[2]}'",
# Python 2.7 and later under Fedora
"TypeError `'NoneType' object has no attribute '__getitem__'' trying to evaluate `${NONE[2]}'",
]
assert str(e) in expect, e
else:
raise AssertionError("did not catch expected UserError")
try:
def func(a, b, c):
pass
scons_subst("${func(1)}", env, gvars={'func':func})
except SCons.Errors.UserError as e:
expect = [
# Python 3.5 (and 3.x?)
"TypeError `func() missing 2 required positional arguments: 'b' and 'c'' trying to evaluate `${func(1)}'",
# Python 3.10
"TypeError `scons_subst_TestCase.test_subst_type_errors.<locals>.func() missing 2 required positional arguments: 'b' and 'c'' trying to evaluate `${func(1)}'",
]
assert str(e) in expect, repr(str(e))
else:
raise AssertionError("did not catch expected UserError")
def test_subst_raw_function(self):
"""Test scons_subst(): fetch function with SUBST_RAW plus conv"""
# Test that the combination of SUBST_RAW plus a pass-through
# conversion routine allows us to fetch a function through the
# dictionary. CommandAction uses this to allow delayed evaluation
# of $SPAWN variables.
env = DummyEnv(self.loc)
gvars = env.Dictionary()
x = lambda x: x
r = scons_subst("$CALLABLE1", env, mode=SUBST_RAW, conv=x, gvars=gvars)
assert r is self.callable_object_1, repr(r)
r = scons_subst("$CALLABLE1", env, mode=SUBST_RAW, gvars=gvars)
assert r == 'callable-1', repr(r)
# Test how we handle overriding the internal conversion routines.
def s(obj):
return obj
n1 = self.MyNode('n1')
env = DummyEnv({'NODE' : n1})
gvars = env.Dictionary()
node = scons_subst("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
assert node is n1, node
node = scons_subst("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
assert node is n1, node
node = scons_subst("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
assert node is n1, node
def test_subst_overriding_gvars(self):
"""Test scons_subst(): supplying an overriding gvars dictionary"""
env = DummyEnv({'XXX' : 'xxx'})
result = scons_subst('$XXX', env, gvars=env.Dictionary())
assert result == 'xxx', result
result = scons_subst('$XXX', env, gvars={'XXX' : 'yyy'})
assert result == 'yyy', result
class CLVar_TestCase(unittest.TestCase):
def test_CLVar(self):
"""Test scons_subst() and scons_subst_list() with CLVar objects"""
loc = {}
loc['FOO'] = 'foo'
loc['BAR'] = SCons.Util.CLVar('bar')
loc['CALL'] = lambda target, source, env, for_signature: 'call'
env = DummyEnv(loc)
cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
assert newcmd == ['test', 'foo', 'bar', 'call', 'test'], newcmd
cmd_list = scons_subst_list(cmd, env, gvars=env.Dictionary())
assert len(cmd_list) == 1, cmd_list
assert cmd_list[0][0] == "test", cmd_list[0][0]
assert cmd_list[0][1] == "foo", cmd_list[0][1]
assert cmd_list[0][2] == "bar", cmd_list[0][2]
assert cmd_list[0][3] == "call", cmd_list[0][3]
assert cmd_list[0][4] == "test", cmd_list[0][4]
class scons_subst_list_TestCase(SubstTestCase):
basic_cases = [
"$TARGETS",
[
["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
],
"$SOURCES $NEWLINE $TARGETS",
[
["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
],
"$SOURCES$NEWLINE",
[
["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
["after"],
],
"foo$FFF",
[
["fooGGG"],
],
"foo${FFF}",
[
["fooGGG"],
],
"test ${SOURCES.attribute.attr1}",
[
["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"],
],
"test ${SOURCES.attribute.attr2}",
[
["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"],
],
"$DO --in=$FOO --out=$BAR",
[
["do something", "--in=foo.in", "--out=bar with spaces.out"],
],
# This test is now fixed, and works like it should.
"$DO --in=$CRAZY --out=$BAR",
[
["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
],
# Try passing a list to scons_subst_list().
[ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
[
["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
],
# Test against a former bug in scons_subst_list().
"$XXX$HHH",
[
["GGGIII"],
],
# Test double-dollar-sign behavior.
"$$FFF$HHH",
[
["$FFFIII"],
],
# Test various combinations of strings, lists and functions.
None, [[]],
[None], [[]],
'', [[]],
[''], [[]],
'x', [['x']],
['x'], [['x']],
'x y', [['x', 'y']],
['x y'], [['x y']],
['x', 'y'], [['x', 'y']],
'$N', [[]],
['$N'], [[]],
'$X', [['x']],
['$X'], [['x']],
'$Y', [['x']],
['$Y'], [['x']],
#'$R', [[]],
#['$R'], [[]],
'$S', [['x', 'y']],
'$S z', [['x', 'y', 'z']],
['$S'], [['x', 'y']],
['$S z'], [['x', 'y z']], # XXX - IS THIS BEST?
['$S', 'z'], [['x', 'y', 'z']],
'$LS', [['x y']],
'$LS z', [['x y', 'z']],
['$LS'], [['x y']],
['$LS z'], [['x y z']],
['$LS', 'z'], [['x y', 'z']],
'$L', [['x', 'y']],
'$L z', [['x', 'y', 'z']],
['$L'], [['x', 'y']],
['$L z'], [['x', 'y z']], # XXX - IS THIS BEST?
['$L', 'z'], [['x', 'y', 'z']],
cs, [['cs']],
[cs], [['cs']],
cl, [['cl']],
[cl], [['cl']],
'$CS', [['cs']],
['$CS'], [['cs']],
'$CL', [['cl']],
['$CL'], [['cl']],
# Various uses of UserString.
collections.UserString('x'), [['x']],
[collections.UserString('x')], [['x']],
collections.UserString('$X'), [['x']],
[collections.UserString('$X')], [['x']],
collections.UserString('$US'), [['us']],
[collections.UserString('$US')], [['us']],
'$US', [['us']],
['$US'], [['us']],
# Test function calls within ${}.
'$FUNCCALL', [['a', 'xc', 'b']],
# Test handling of newlines in white space.
'foo\nbar', [['foo'], ['bar']],
'foo\n\nbar', [['foo'], ['bar']],
'foo \n \n bar', [['foo'], ['bar']],
'foo \nmiddle\n bar', [['foo'], ['middle'], ['bar']],
# Bug reported by Christoph Wiedemann.
cvt('$xxx/bin'), [['/bin']],
# Test variables smooshed together with different prefixes.
'foo$AAA', [['fooa']],
'<$AAA', [['<', 'a']],
'>$AAA', [['>', 'a']],
'|$AAA', [['|', 'a']],
# Test callables that don't match our calling arguments.
'$CALLABLE2', [['callable-2']],
# Test handling of quotes.
# XXX Find a way to handle this in the future.
#'aaa "bbb ccc" ddd', [['aaa', 'bbb ccc', 'ddd']],
'${_defines(DEFS)}', [['Q1="q1"', 'Q2="a"']],
]
def test_scons_subst_list(self):
"""Test scons_subst_list(): basic substitution"""
def convert_lists(expect):
return [list(map(cvt, l)) for l in expect]
return self.basic_comparisons(scons_subst_list, convert_lists)
subst_list_cases = [
"test $xxx",
[["test"]],
[["test"]],
[["test"]],
"test $($xxx$)",
[["test", "$($)"]],
[["test"]],
[["test"]],
"test $( $xxx $)",
[["test", "$(", "$)"]],
[["test"]],
[["test"]],
"$AAA ${AAA}A $BBBB $BBB",
[["a", "aA", "b"]],
[["a", "aA", "b"]],
[["a", "aA", "b"]],
"$RECURSE",
[["foo", "bar"]],
[["foo", "bar"]],
[["foo", "bar"]],
"$RRR",
[["foo", "bar"]],
[["foo", "bar"]],
[["foo", "bar"]],
# Verify what happens with no target or source nodes.
"$TARGET $SOURCES",
[[]],
[[]],
[[]],
"$TARGETS $SOURCE",
[[]],
[[]],
[[]],
# Various test refactored from ActionTests.py
"${LIST}",
[['This', 'is', '$(', '$)', 'test']],
[['This', 'is', 'test']],
[['This', 'is', 'test']],
["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
[["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]],
[["|", "a", "|", "b", "|", "c", "1"]],
[["|", "|", "c", "1"]],
]
def test_subst_env(self):
"""Test scons_subst_list(): expansion dictionary"""
# The expansion dictionary no longer comes from the construction
# environment automatically.
env = DummyEnv()
s = scons_subst_list('$AAA', env)
assert s == [[]], s
def test_subst_target_source(self):
"""Test scons_subst_list(): target= and source= arguments"""
env = DummyEnv(self.loc)
gvars = env.Dictionary()
t1 = self.MyNode('t1')
t2 = self.MyNode('t2')
s1 = self.MyNode('s1')
s2 = self.MyNode('s2')
result = scons_subst_list("$TARGET $SOURCES", env,
target=[t1, t2],
source=[s1, s2],
gvars=gvars)
assert result == [['t1', 's1', 's2']], result
result = scons_subst_list("$TARGET $SOURCES", env,
target=[t1, t2],
source=[s1, s2],
gvars={})
assert result == [['t1', 's1', 's2']], result
# Test interpolating a callable.
_t = DummyNode('t')
_s = DummyNode('s')
cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES",
env, target=_t, source=_s,
gvars=gvars)
assert cmd_list == [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list
def test_subst_escape(self):
"""Test scons_subst_list(): escape functionality"""
env = DummyEnv(self.loc)
gvars = env.Dictionary()
def escape_func(foo):
return '**' + foo + '**'
cmd_list = scons_subst_list("abc $LITERALS xyz", env, gvars=gvars)
assert cmd_list == [['abc',
'foo\nwith\nnewlines',
'bar\nwith\nnewlines',
'xyz']], cmd_list
c = cmd_list[0][0].escape(escape_func)
assert c == 'abc', c
c = cmd_list[0][1].escape(escape_func)
assert c == '**foo\nwith\nnewlines**', c
c = cmd_list[0][2].escape(escape_func)
assert c == '**bar\nwith\nnewlines**', c
c = cmd_list[0][3].escape(escape_func)
assert c == 'xyz', c
# We used to treat literals smooshed together like the whole
# thing was literal and escape it as a unit. The commented-out
# asserts below are in case we ever have to find a way to
# resurrect that functionality in some way.
cmd_list = scons_subst_list("abc${LITERALS}xyz", env, gvars=gvars)
c = cmd_list[0][0].escape(escape_func)
#assert c == '**abcfoo\nwith\nnewlines**', c
assert c == 'abcfoo\nwith\nnewlines', c
c = cmd_list[0][1].escape(escape_func)
#assert c == '**bar\nwith\nnewlinesxyz**', c
assert c == 'bar\nwith\nnewlinesxyz', c
_t = DummyNode('t')
cmd_list = scons_subst_list('echo "target: $TARGET"', env,
target=_t, gvars=gvars)
c = cmd_list[0][0].escape(escape_func)
assert c == 'echo', c
c = cmd_list[0][1].escape(escape_func)
assert c == '"target:', c
c = cmd_list[0][2].escape(escape_func)
assert c == 't"', c
def test_subst_SUBST_modes(self):
"""Test scons_subst_list(): SUBST_* modes"""
env = DummyEnv(self.loc)
subst_list_cases = self.subst_list_cases[:]
gvars = env.Dictionary()
r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW, gvars=gvars)
assert r == [[]], r
failed = 0
while subst_list_cases:
input, eraw, ecmd, esig = subst_list_cases[:4]
result = scons_subst_list(input, env, mode=SUBST_RAW, gvars=gvars)
if result != eraw:
if failed == 0: print()
print(" input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw)))
failed = failed + 1
result = scons_subst_list(input, env, mode=SUBST_CMD, gvars=gvars)
if result != ecmd:
if failed == 0: print()
print(" input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd)))
failed = failed + 1
result = scons_subst_list(input, env, mode=SUBST_SIG, gvars=gvars)
if result != esig:
if failed == 0: print()
print(" input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig)))
failed = failed + 1
del subst_list_cases[:4]
assert failed == 0, "%d subst() mode cases failed" % failed
def test_subst_attribute_errors(self):
"""Test scons_subst_list(): handling attribute errors"""
env = DummyEnv()
try:
class Foo:
pass
scons_subst_list('${foo.bar}', env, gvars={'foo':Foo()})
except SCons.Errors.UserError as e:
expect = [
"AttributeError `bar' trying to evaluate `${foo.bar}'",
"AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
"AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'",
"AttributeError `'Foo' object has no attribute 'bar'' trying to evaluate `${foo.bar}'",
]
assert str(e) in expect, e
else:
raise AssertionError("did not catch expected UserError")
def test_subst_syntax_errors(self):
"""Test scons_subst_list(): handling syntax errors"""
env = DummyEnv()
try:
scons_subst_list('$foo.bar.3.0', env)
except SCons.Errors.UserError as e:
expect = [
# Python 2.5 to 3.9
"SyntaxError `invalid syntax (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
# Python 3.10 and later
"SyntaxError `invalid syntax. Perhaps you forgot a comma? (<string>, line 1)' trying to evaluate `$foo.bar.3.0'",
]
assert str(e) in expect, e
else:
raise AssertionError("did not catch expected SyntaxError")
def test_subst_raw_function(self):
"""Test scons_subst_list(): fetch function with SUBST_RAW plus conv"""
# Test that the combination of SUBST_RAW plus a pass-through
# conversion routine allows us to fetch a function through the
# dictionary.
env = DummyEnv(self.loc)
gvars = env.Dictionary()
x = lambda x: x
r = scons_subst_list("$CALLABLE2", env, mode=SUBST_RAW, conv=x, gvars=gvars)
assert r == [[self.callable_object_2]], repr(r)
r = scons_subst_list("$CALLABLE2", env, mode=SUBST_RAW, gvars=gvars)
assert r == [['callable-2']], repr(r)
def test_subst_list_overriding_gvars(self):
"""Test scons_subst_list(): overriding conv()"""
env = DummyEnv()
def s(obj):
return obj
n1 = self.MyNode('n1')
env = DummyEnv({'NODE' : n1})
gvars=env.Dictionary()
node = scons_subst_list("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
assert node == [[n1]], node
node = scons_subst_list("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
assert node == [[n1]], node
node = scons_subst_list("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
assert node == [[n1]], node
def test_subst_list_overriding_gvars2(self):
"""Test scons_subst_list(): supplying an overriding gvars dictionary"""
env = DummyEnv({'XXX' : 'xxx'})
result = scons_subst_list('$XXX', env, gvars=env.Dictionary())
assert result == [['xxx']], result
result = scons_subst_list('$XXX', env, gvars={'XXX' : 'yyy'})
assert result == [['yyy']], result
class scons_subst_once_TestCase(unittest.TestCase):
loc = {
'CCFLAGS' : '-DFOO',
'ONE' : 1,
'RECURSE' : 'r $RECURSE r',
'LIST' : ['a', 'b', 'c'],
}
basic_cases = [
'$CCFLAGS -DBAR',
'OTHER_KEY',
'$CCFLAGS -DBAR',
'$CCFLAGS -DBAR',
'CCFLAGS',
'-DFOO -DBAR',
'x $ONE y',
'ONE',
'x 1 y',
'x $RECURSE y',
'RECURSE',
'x r $RECURSE r y',
'$LIST',
'LIST',
'a b c',
['$LIST'],
'LIST',
['a', 'b', 'c'],
['x', '$LIST', 'y'],
'LIST',
['x', 'a', 'b', 'c', 'y'],
['x', 'x $LIST y', 'y'],
'LIST',
['x', 'x a b c y', 'y'],
['x', 'x $CCFLAGS y', 'y'],
'LIST',
['x', 'x $CCFLAGS y', 'y'],
['x', 'x $RECURSE y', 'y'],
'LIST',
['x', 'x $RECURSE y', 'y'],
]
def test_subst_once(self):
"""Test the scons_subst_once() function"""
env = DummyEnv(self.loc)
cases = self.basic_cases[:]
failed = 0
while cases:
input, key, expect = cases[:3]
result = scons_subst_once(input, env, key)
if result != expect:
if failed == 0: print()
print(" input %s (%s) => %s did not match %s" % (repr(input), repr(key), repr(result), repr(expect)))
failed = failed + 1
del cases[:3]
assert failed == 0, "%d subst() cases failed" % failed
class quote_spaces_TestCase(unittest.TestCase):
def test_quote_spaces(self):
"""Test the quote_spaces() method..."""
q = quote_spaces('x')
assert q == 'x', q
q = quote_spaces('x x')
assert q == '"x x"', q
q = quote_spaces('x\tx')
assert q == '"x\tx"', q
class Node:
def __init__(self, name, children=[]):
self.children = children
self.name = name
def __str__(self):
return self.name
def exists(self):
return 1
def rexists(self):
return 1
def has_builder(self):
return 1
def has_explicit_builder(self):
return 1
def side_effect(self):
return 1
def precious(self):
return 1
def always_build(self):
return 1
def current(self):
return 1
class LiteralTestCase(unittest.TestCase):
def test_Literal(self):
"""Test the Literal() function."""
input_list = [ '$FOO', Literal('$BAR') ]
gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
def escape_func(cmd):
return '**' + cmd + '**'
cmd_list = scons_subst_list(input_list, None, gvars=gvars)
cmd_list = escape_list(cmd_list[0], escape_func)
assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
def test_LiteralEqualsTest(self):
"""Test that Literals compare for equality properly"""
assert Literal('a literal') == Literal('a literal')
assert Literal('a literal') != Literal('b literal')
class SpecialAttrWrapperTestCase(unittest.TestCase):
def test_SpecialAttrWrapper(self):
"""Test the SpecialAttrWrapper() function."""
input_list = [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
def escape_func(cmd):
return '**' + cmd + '**'
cmd_list = scons_subst_list(input_list, None, gvars=gvars)
cmd_list = escape_list(cmd_list[0], escape_func)
assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
cmd_list = scons_subst_list(input_list, None, mode=SUBST_SIG, gvars=gvars)
cmd_list = escape_list(cmd_list[0], escape_func)
assert cmd_list == ['BAZ', '**BLEH**'], cmd_list
class subst_dict_TestCase(unittest.TestCase):
def test_subst_dict(self):
"""Test substituting dictionary values in an Action
"""
t = DummyNode('t')
s = DummyNode('s')
d = subst_dict(target=t, source=s)
assert str(d['TARGETS'][0]) == 't', d['TARGETS']
assert str(d['TARGET']) == 't', d['TARGET']
assert str(d['SOURCES'][0]) == 's', d['SOURCES']
assert str(d['SOURCE']) == 's', d['SOURCE']
t1 = DummyNode('t1')
t2 = DummyNode('t2')
s1 = DummyNode('s1')
s2 = DummyNode('s2')
d = subst_dict(target=[t1, t2], source=[s1, s2])
TARGETS = sorted([str(x) for x in d['TARGETS']])
assert TARGETS == ['t1', 't2'], d['TARGETS']
assert str(d['TARGET']) == 't1', d['TARGET']
SOURCES = sorted([str(x) for x in d['SOURCES']])
assert SOURCES == ['s1', 's2'], d['SOURCES']
assert str(d['SOURCE']) == 's1', d['SOURCE']
class V:
# Fake Value node with no rfile() method.
def __init__(self, name):
self.name = name
def __str__(self):
return 'v-'+self.name
def get_subst_proxy(self):
return self
class N(V):
def rfile(self):
return self.__class__('rstr-' + self.name)
t3 = N('t3')
t4 = DummyNode('t4')
t5 = V('t5')
s3 = DummyNode('s3')
s4 = N('s4')
s5 = V('s5')
d = subst_dict(target=[t3, t4, t5], source=[s3, s4, s5])
TARGETS = sorted([str(x) for x in d['TARGETS']])
assert TARGETS == ['t4', 'v-t3', 'v-t5'], TARGETS
SOURCES = sorted([str(x) for x in d['SOURCES']])
assert SOURCES == ['s3', 'v-rstr-s4', 'v-s5'], SOURCES
if __name__ == "__main__":
unittest.main()
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4: