38eb0cacf65818cbabbfb67625db3fd04b4cd15b
[scons.git] / src / engine / SCons / BuilderTests.py
1 #
2 # __COPYRIGHT__
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 #
23
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
25
26 import SCons.compat
27
28 # Define a null function for use as a builder action.
29 # Where this is defined in the file seems to affect its
30 # byte-code contents, so try to minimize changes by
31 # defining it here, before we even import anything.
32 def Func():
33     pass
34
35 import collections
36 import io
37 import os.path
38 import re
39 import sys
40 import unittest
41
42 import TestCmd
43
44 import SCons.Action
45 import SCons.Builder
46 import SCons.Environment
47 import SCons.Errors
48 import SCons.Subst
49 import SCons.Util
50
51 sys.stdout = io.StringIO()
52
53 # Initial setup of the common environment for all tests,
54 # a temporary working directory containing a
55 # script for writing arguments to an output file.
56 #
57 # We don't do this as a setUp() method because it's
58 # unnecessary to create a separate directory and script
59 # for each test, they can just use the one.
60 test = TestCmd.TestCmd(workdir = '')
61
62 outfile = test.workpath('outfile')
63 outfile2 = test.workpath('outfile2')
64
65 infile = test.workpath('infile')
66 test.write(infile, "infile\n")
67
68 show_string = None
69
70 scons_env = SCons.Environment.Environment()
71
72 env_arg2nodes_called = None
73
74 class Environment:
75     def __init__(self, **kw):
76         self.d = {}
77         self.d['SHELL'] = scons_env['SHELL']
78         self.d['SPAWN'] = scons_env['SPAWN']
79         self.d['ESCAPE'] = scons_env['ESCAPE']
80         for k, v in kw.items():
81             self.d[k] = v
82         global env_arg2nodes_called
83         env_arg2nodes_called = None
84         self.scanner = None
85         self.fs = SCons.Node.FS.FS()
86     def subst(self, s):
87         if not SCons.Util.is_String(s):
88             return s
89         def substitute(m, d=self.d):
90             return d.get(m.group(1), '')
91         return re.sub(r'\$(\w+)', substitute, s)
92     def subst_target_source(self, string, raw=0, target=None,
93                             source=None, dict=None, conv=None):
94         return SCons.Subst.scons_subst(string, self, raw, target,
95                                        source, dict, conv)
96     def subst_list(self, string, raw=0, target=None, source=None, conv=None):
97         return SCons.Subst.scons_subst_list(string, self, raw, target,
98                                             source, {}, {}, conv)
99     def arg2nodes(self, args, factory, **kw):
100         global env_arg2nodes_called
101         env_arg2nodes_called = 1
102         if not SCons.Util.is_List(args):
103             args = [args]
104         list = []
105         for a in args:
106             if SCons.Util.is_String(a):
107                 a = factory(self.subst(a))
108             list.append(a)
109         return list
110     def get_factory(self, factory):
111         return factory or self.fs.File
112     def get_scanner(self, ext):
113         return self.scanner
114     def Dictionary(self):
115         return {}
116     def autogenerate(self, dir=''):
117         return {}
118     def __setitem__(self, item, var):
119         self.d[item] = var
120     def __getitem__(self, item):
121         return self.d[item]
122     def __contains__(self, item):
123         return self.d.__contains__(item)
124     def has_key(self, item):
125         return item in self.d
126     def keys(self):
127         return self.d.keys()
128     def get(self, key, value=None):
129         return self.d.get(key, value)
130     def Override(self, overrides):
131         env = Environment(**self.d)
132         env.d.update(overrides)
133         env.scanner = self.scanner
134         return env
135     def _update(self, dict):
136         self.d.update(dict)
137     def items(self):
138         return self.d.items()
139     def sig_dict(self):
140         d = {}
141         for k,v in self.items(): d[k] = v
142         d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
143         d['TARGET'] = d['TARGETS'][0]
144         d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
145         d['SOURCE'] = d['SOURCES'][0]
146         return d
147     def __cmp__(self, other):
148         return cmp(self.scanner, other.scanner) or cmp(self.d, other.d)
149
150 class MyAction:
151     def __init__(self, action):
152         self.action = action
153     def __call__(self, *args, **kw):
154         pass
155     def get_executor(self, env, overrides, tlist, slist, executor_kw):
156         return ['executor'] + [self.action]
157
158 class MyNode_without_target_from_source:
159     def __init__(self, name):
160         self.name = name
161         self.sources = []
162         self.builder = None
163         self.is_explicit = None
164         self.side_effect = 0
165         self.suffix = os.path.splitext(name)[1]
166     def disambiguate(self):
167         return self
168     def __str__(self):
169         return self.name
170     def builder_set(self, builder):
171         self.builder = builder
172     def has_builder(self):
173         return not self.builder is None
174     def set_explicit(self, is_explicit):
175         self.is_explicit = is_explicit
176     def has_explicit_builder(self):
177         return self.is_explicit
178     def env_set(self, env, safe=0):
179         self.env = env
180     def add_source(self, source):
181         self.sources.extend(source)
182     def scanner_key(self):
183         return self.name
184     def is_derived(self):
185         return self.has_builder()
186     def generate_build_env(self, env):
187         return env
188     def get_build_env(self):
189         return self.executor.get_build_env()
190     def set_executor(self, executor):
191         self.executor = executor
192     def get_executor(self, create=1):
193         return self.executor
194
195 class MyNode(MyNode_without_target_from_source):
196     def target_from_source(self, prefix, suffix, stripext):
197         return MyNode(prefix + stripext(str(self))[0] + suffix)
198
199 class BuilderTestCase(unittest.TestCase):
200
201     def test__init__(self):
202         """Test simple Builder creation
203         """
204         builder = SCons.Builder.Builder(action="foo")
205         assert not builder is None, builder
206         builder = SCons.Builder.Builder(action="foo", OVERRIDE='x')
207         x = builder.overrides['OVERRIDE']
208         assert x == 'x', x
209
210     def test__nonzero__(self):
211         """Test a builder raising an exception when __nonzero__ is called
212         """
213         builder = SCons.Builder.Builder(action="foo")
214         exc_caught = None
215         try:
216             builder.__nonzero__()
217         except SCons.Errors.InternalError:
218             exc_caught = 1
219         assert exc_caught, "did not catch expected InternalError exception"
220
221         class Node:
222              pass
223
224         n = Node()
225         n.builder = builder
226         exc_caught = None
227         try:
228             if n.builder:
229                 pass
230         except SCons.Errors.InternalError:
231             exc_caught = 1
232         assert exc_caught, "did not catch expected InternalError exception"
233
234     def test__call__(self):
235         """Test calling a builder to establish source dependencies
236         """
237         env = Environment()
238         builder = SCons.Builder.Builder(action="foo",
239                                         target_factory=MyNode,
240                                         source_factory=MyNode)
241
242         tgt = builder(env, source=[])
243         assert tgt == [], tgt
244
245         n1 = MyNode("n1")
246         n2 = MyNode("n2")
247         builder(env, target = n1, source = n2)
248         assert env_arg2nodes_called
249         assert n1.env == env, n1.env
250         assert n1.builder == builder, n1.builder
251         assert n1.sources == [n2], n1.sources
252         assert n1.executor, "no executor found"
253         assert not hasattr(n2, 'env')
254
255         l = [1]
256         ul = collections.UserList([2])
257         try:
258             l.extend(ul)
259         except TypeError:
260             def mystr(l):
261                 return str(list(map(str, l)))
262         else:
263             mystr = str
264
265         nnn1 = MyNode("nnn1")
266         nnn2 = MyNode("nnn2")
267         tlist = builder(env, target = [nnn1, nnn2], source = [])
268         s = mystr(tlist)
269         assert s == "['nnn1', 'nnn2']", s
270         l = list(map(str, tlist))
271         assert l == ['nnn1', 'nnn2'], l
272
273         tlist = builder(env, target = 'n3', source = 'n4')
274         s = mystr(tlist)
275         assert s == "['n3']", s
276         target = tlist[0]
277         l = list(map(str, tlist))
278         assert l == ['n3'], l
279         assert target.name == 'n3'
280         assert target.sources[0].name == 'n4'
281
282         tlist = builder(env, target = 'n4 n5', source = ['n6 n7'])
283         s = mystr(tlist)
284         assert s == "['n4 n5']", s
285         l = list(map(str, tlist))
286         assert l == ['n4 n5'], l
287         target = tlist[0]
288         assert target.name == 'n4 n5'
289         assert target.sources[0].name == 'n6 n7'
290
291         tlist = builder(env, target = ['n8 n9'], source = 'n10 n11')
292         s = mystr(tlist)
293         assert s == "['n8 n9']", s
294         l = list(map(str, tlist))
295         assert l == ['n8 n9'], l
296         target = tlist[0]
297         assert target.name == 'n8 n9'
298         assert target.sources[0].name == 'n10 n11'
299
300         # A test to be uncommented when we freeze the environment
301         # as part of calling the builder.
302         #env1 = Environment(VAR='foo')
303         #target = builder(env1, target = 'n12', source = 'n13')
304         #env1['VAR'] = 'bar'
305         #be = target.get_build_env()
306         #assert be['VAR'] == 'foo', be['VAR']
307
308         try: unicode
309         except NameError:
310             uni = str
311         else:
312             uni = unicode
313
314         target = builder(env, target = uni('n12 n13'),
315                           source = [uni('n14 n15')])[0]
316         assert target.name == uni('n12 n13')
317         assert target.sources[0].name == uni('n14 n15')
318
319         target = builder(env, target = [uni('n16 n17')],
320                          source = uni('n18 n19'))[0]
321         assert target.name == uni('n16 n17')
322         assert target.sources[0].name == uni('n18 n19')
323
324         n20 = MyNode_without_target_from_source('n20')
325         flag = 0
326         try:
327             target = builder(env, None, source=n20)
328         except SCons.Errors.UserError, e:
329             flag = 1
330         assert flag, "UserError should be thrown if a source node can't create a target."
331
332         builder = SCons.Builder.Builder(action="foo",
333                                         target_factory=MyNode,
334                                         source_factory=MyNode,
335                                         prefix='p-',
336                                         suffix='.s')
337         target = builder(env, None, source='n21')[0]
338         assert target.name == 'p-n21.s', target
339
340         builder = SCons.Builder.Builder(misspelled_action="foo",
341                                         suffix = '.s')
342         try:
343             builder(env, target = 'n22', source = 'n22')
344         except SCons.Errors.UserError, e:
345             pass
346         else:
347             raise "Did not catch expected UserError."
348
349         builder = SCons.Builder.Builder(action="foo")
350         target = builder(env, None, source='n22', srcdir='src_dir')[0]
351         p = target.sources[0].path
352         assert p == os.path.join('src_dir', 'n22'), p
353
354     def test_mistaken_variables(self):
355         """Test keyword arguments that are often mistakes
356         """
357         import SCons.Warnings
358         env = Environment()
359         builder = SCons.Builder.Builder(action="foo")
360
361         save_warn = SCons.Warnings.warn
362         warned = []
363         def my_warn(exception, warning, warned=warned):
364             warned.append(warning)
365         SCons.Warnings.warn = my_warn
366
367         try:
368             target = builder(env, 'mistaken1', sources='mistaken1.c')
369             assert warned == ["Did you mean to use `source' instead of `sources'?"], warned
370             del warned[:]
371
372             target = builder(env, 'mistaken2', targets='mistaken2.c')
373             assert warned == ["Did you mean to use `target' instead of `targets'?"], warned
374             del warned[:]
375
376             target = builder(env, 'mistaken3', targets='mistaken3', sources='mistaken3.c')
377             assert "Did you mean to use `source' instead of `sources'?" in warned, warned
378             assert "Did you mean to use `target' instead of `targets'?" in warned, warned
379             del warned[:]
380         finally:
381             SCons.Warnings.warn = save_warn
382
383     def test_action(self):
384         """Test Builder creation
385
386         Verify that we can retrieve the supplied action attribute.
387         """
388         builder = SCons.Builder.Builder(action="foo")
389         assert builder.action.cmd_list == "foo"
390
391         def func():
392             pass
393         builder = SCons.Builder.Builder(action=func)
394         assert isinstance(builder.action, SCons.Action.FunctionAction)
395         # Preserve the following so that the baseline test will fail.
396         # Remove it in favor of the previous test at some convenient
397         # point in the future.
398         assert builder.action.execfunction == func
399
400     def test_generator(self):
401         """Test Builder creation given a generator function."""
402
403         def generator():
404             pass
405
406         builder = SCons.Builder.Builder(generator=generator)
407         assert builder.action.generator == generator
408
409     def test_get_name(self):
410         """Test the get_name() method
411         """
412
413     def test_cmp(self):
414         """Test simple comparisons of Builder objects
415         """
416         b1 = SCons.Builder.Builder(src_suffix = '.o')
417         b2 = SCons.Builder.Builder(src_suffix = '.o')
418         assert b1 == b2
419         b3 = SCons.Builder.Builder(src_suffix = '.x')
420         assert b1 != b3
421         assert b2 != b3
422
423     def test_target_factory(self):
424         """Test a Builder that creates target nodes of a specified class
425         """
426         class Foo:
427             pass
428         def FooFactory(target):
429             global Foo
430             return Foo(target)
431         builder = SCons.Builder.Builder(target_factory = FooFactory)
432         assert builder.target_factory is FooFactory
433         assert not builder.source_factory is FooFactory
434
435     def test_source_factory(self):
436         """Test a Builder that creates source nodes of a specified class
437         """
438         class Foo:
439             pass
440         def FooFactory(source):
441             global Foo
442             return Foo(source)
443         builder = SCons.Builder.Builder(source_factory = FooFactory)
444         assert not builder.target_factory is FooFactory
445         assert builder.source_factory is FooFactory
446
447     def test_splitext(self):
448         """Test the splitext() method attached to a Builder."""
449         b = SCons.Builder.Builder()
450         assert b.splitext('foo') == ('foo','')
451         assert b.splitext('foo.bar') == ('foo','.bar')
452         assert b.splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'')
453
454         class MyBuilder(SCons.Builder.BuilderBase):
455             def splitext(self, path):
456                 return "called splitext()"
457
458         b = MyBuilder()
459         ret = b.splitext('xyz.c')
460         assert ret == "called splitext()", ret
461
462     def test_adjust_suffix(self):
463         """Test how a Builder adjusts file suffixes
464         """
465         b = SCons.Builder.Builder()
466         assert b.adjust_suffix('.foo') == '.foo'
467         assert b.adjust_suffix('foo') == '.foo'
468         assert b.adjust_suffix('$foo') == '$foo'
469
470         class MyBuilder(SCons.Builder.BuilderBase):
471             def adjust_suffix(self, suff):
472                 return "called adjust_suffix()"
473
474         b = MyBuilder()
475         ret = b.adjust_suffix('.foo')
476         assert ret == "called adjust_suffix()", ret
477
478     def test_prefix(self):
479         """Test Builder creation with a specified target prefix
480
481         Make sure that there is no '.' separator appended.
482         """
483         env = Environment()
484         builder = SCons.Builder.Builder(prefix = 'lib.')
485         assert builder.get_prefix(env) == 'lib.'
486         builder = SCons.Builder.Builder(prefix = 'lib', action='')
487         assert builder.get_prefix(env) == 'lib'
488         tgt = builder(env, target = 'tgt1', source = 'src1')[0]
489         assert tgt.path == 'libtgt1', \
490                 "Target has unexpected name: %s" % tgt.path
491         tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')[0]
492         assert tgt.path == 'libtgt2a tgt2b', \
493                 "Target has unexpected name: %s" % tgt.path
494         tgt = builder(env, target = None, source = 'src3')[0]
495         assert tgt.path == 'libsrc3', \
496                 "Target has unexpected name: %s" % tgt.path
497         tgt = builder(env, target = None, source = 'lib/src4')[0]
498         assert tgt.path == os.path.join('lib', 'libsrc4'), \
499                 "Target has unexpected name: %s" % tgt.path
500         tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')[0]
501         assert tgt.path == os.path.join('lib', 'libtgt5'), \
502                 "Target has unexpected name: %s" % tgt.path
503
504         def gen_prefix(env, sources):
505             return "gen_prefix() says " + env['FOO']
506         my_env = Environment(FOO = 'xyzzy')
507         builder = SCons.Builder.Builder(prefix = gen_prefix)
508         assert builder.get_prefix(my_env) == "gen_prefix() says xyzzy"
509         my_env['FOO'] = 'abracadabra'
510         assert builder.get_prefix(my_env) == "gen_prefix() says abracadabra"
511
512         def my_emit(env, sources):
513             return env.subst('$EMIT')
514         my_env = Environment(FOO = '.foo', EMIT = 'emit-')
515         builder = SCons.Builder.Builder(prefix = {None   : 'default-',
516                                                   '.in'  : 'out-',
517                                                   '.x'   : 'y-',
518                                                   '$FOO' : 'foo-',
519                                                   '.zzz' : my_emit},
520                                         action = '')
521         tgt = builder(my_env, target = None, source = 'f1')[0]
522         assert tgt.path == 'default-f1', tgt.path
523         tgt = builder(my_env, target = None, source = 'f2.c')[0]
524         assert tgt.path == 'default-f2', tgt.path
525         tgt = builder(my_env, target = None, source = 'f3.in')[0]
526         assert tgt.path == 'out-f3', tgt.path
527         tgt = builder(my_env, target = None, source = 'f4.x')[0]
528         assert tgt.path == 'y-f4', tgt.path
529         tgt = builder(my_env, target = None, source = 'f5.foo')[0]
530         assert tgt.path == 'foo-f5', tgt.path
531         tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
532         assert tgt.path == 'emit-f6', tgt.path
533
534     def test_set_suffix(self):
535         """Test the set_suffix() method"""
536         b = SCons.Builder.Builder(action='')
537         env = Environment(XSUFFIX = '.x')
538
539         s = b.get_suffix(env)
540         assert s == '', s
541
542         b.set_suffix('.foo')
543         s = b.get_suffix(env)
544         assert s == '.foo', s
545
546         b.set_suffix('$XSUFFIX')
547         s = b.get_suffix(env)
548         assert s == '.x', s
549
550     def test_src_suffix(self):
551         """Test Builder creation with a specified source file suffix
552         
553         Make sure that the '.' separator is appended to the
554         beginning if it isn't already present.
555         """
556         env = Environment(XSUFFIX = '.x', YSUFFIX = '.y')
557
558         b1 = SCons.Builder.Builder(src_suffix = '.c', action='')
559         assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env)
560
561         tgt = b1(env, target = 'tgt2', source = 'src2')[0]
562         assert tgt.sources[0].path == 'src2.c', \
563                 "Source has unexpected name: %s" % tgt.sources[0].path
564
565         tgt = b1(env, target = 'tgt3', source = 'src3a src3b')[0]
566         assert len(tgt.sources) == 1
567         assert tgt.sources[0].path == 'src3a src3b.c', \
568                 "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].path
569
570         b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = b1)
571         r = sorted(b2.src_suffixes(env))
572         assert r == ['.2', '.c'], r
573
574         b3 = SCons.Builder.Builder(action = {'.3a' : '', '.3b' : ''})
575         s = sorted(b3.src_suffixes(env))
576         assert s == ['.3a', '.3b'], s
577
578         b4 = SCons.Builder.Builder(src_suffix = '$XSUFFIX')
579         assert b4.src_suffixes(env) == ['.x'], b4.src_suffixes(env)
580
581         b5 = SCons.Builder.Builder(action = { '.y' : ''})
582         assert b5.src_suffixes(env) == ['.y'], b5.src_suffixes(env)
583
584     def test_srcsuffix_nonext(self):
585         "Test target generation from non-extension source suffixes"
586         env = Environment()
587         b6 = SCons.Builder.Builder(action = '',
588                                    src_suffix='_src.a',
589                                    suffix='.b')
590         tgt = b6(env, target=None, source='foo_src.a')
591         assert str(tgt[0]) == 'foo.b', str(tgt[0])
592
593         b7 = SCons.Builder.Builder(action = '',
594                                    src_suffix='_source.a',
595                                    suffix='_obj.b')
596         b8 = SCons.Builder.Builder(action = '',
597                                    src_builder=b7,
598                                    suffix='.c')
599         tgt = b8(env, target=None, source='foo_source.a')
600         assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
601         src = env.fs.File('foo_source.a')
602         tgt = b8(env, target=None, source=src)
603         assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
604
605         b9 = SCons.Builder.Builder(action={'_src.a' : 'srcaction'},
606                                    suffix='.c')
607         b9.add_action('_altsrc.b', 'altaction')
608         tgt = b9(env, target=None, source='foo_altsrc.b')
609         assert str(tgt[0]) == 'foo.c', str(tgt[0])
610
611     def test_src_suffix_expansion(self):
612         """Test handling source suffixes when an expansion is involved"""
613         env = Environment(OBJSUFFIX = '.obj')
614
615         b1 = SCons.Builder.Builder(action = '',
616                                    src_suffix='.c',
617                                    suffix='.obj')
618         b2 = SCons.Builder.Builder(action = '',
619                                    src_builder=b1,
620                                    src_suffix='.obj',
621                                    suffix='.exe')
622         tgt = b2(env, target=None, source=['foo$OBJSUFFIX'])
623         s = list(map(str, tgt[0].sources))
624         assert s == ['foo.obj'], s
625
626     def test_suffix(self):
627         """Test Builder creation with a specified target suffix
628
629         Make sure that the '.' separator is appended to the
630         beginning if it isn't already present.
631         """
632         env = Environment()
633         builder = SCons.Builder.Builder(suffix = '.o')
634         assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
635         builder = SCons.Builder.Builder(suffix = 'o', action='')
636         assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
637         tgt = builder(env, target = 'tgt3', source = 'src3')[0]
638         assert tgt.path == 'tgt3.o', \
639                 "Target has unexpected name: %s" % tgt.path
640         tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')[0]
641         assert tgt.path == 'tgt4a tgt4b.o', \
642                 "Target has unexpected name: %s" % tgt.path
643         tgt = builder(env, target = None, source = 'src5')[0]
644         assert tgt.path == 'src5.o', \
645                 "Target has unexpected name: %s" % tgt.path
646
647         def gen_suffix(env, sources):
648             return "gen_suffix() says " + env['BAR']
649         my_env = Environment(BAR = 'hocus pocus')
650         builder = SCons.Builder.Builder(suffix = gen_suffix)
651         assert builder.get_suffix(my_env) == "gen_suffix() says hocus pocus", builder.get_suffix(my_env)
652         my_env['BAR'] = 'presto chango'
653         assert builder.get_suffix(my_env) == "gen_suffix() says presto chango"
654
655         def my_emit(env, sources):
656             return env.subst('$EMIT')
657         my_env = Environment(BAR = '.bar', EMIT = '.emit')
658         builder = SCons.Builder.Builder(suffix = {None   : '.default',
659                                                   '.in'  : '.out',
660                                                   '.x'   : '.y',
661                                                   '$BAR' : '.new',
662                                                   '.zzz' : my_emit},
663                                         action='')
664         tgt = builder(my_env, target = None, source = 'f1')[0]
665         assert tgt.path == 'f1.default', tgt.path
666         tgt = builder(my_env, target = None, source = 'f2.c')[0]
667         assert tgt.path == 'f2.default', tgt.path
668         tgt = builder(my_env, target = None, source = 'f3.in')[0]
669         assert tgt.path == 'f3.out', tgt.path
670         tgt = builder(my_env, target = None, source = 'f4.x')[0]
671         assert tgt.path == 'f4.y', tgt.path
672         tgt = builder(my_env, target = None, source = 'f5.bar')[0]
673         assert tgt.path == 'f5.new', tgt.path
674         tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
675         assert tgt.path == 'f6.emit', tgt.path
676
677     def test_single_source(self):
678         """Test Builder with single_source flag set"""
679         def func(target, source, env):
680             open(str(target[0]), "w")
681             if (len(source) == 1 and len(target) == 1):
682                 env['CNT'][0] = env['CNT'][0] + 1
683                 
684         env = Environment()
685         infiles = []
686         outfiles = []
687         for i in range(10):
688             infiles.append(test.workpath('%d.in' % i))
689             outfiles.append(test.workpath('%d.out' % i))
690             test.write(infiles[-1], "\n")
691         builder = SCons.Builder.Builder(action=SCons.Action.Action(func,None),
692                                         single_source = 1, suffix='.out')
693         env['CNT'] = [0]
694         tgt = builder(env, target=outfiles[0], source=infiles[0])[0]
695         s = str(tgt)
696         t = os.path.normcase(test.workpath('0.out'))
697         assert os.path.normcase(s) == t, s
698         tgt.prepare()
699         tgt.build()
700         assert env['CNT'][0] == 1, env['CNT'][0]
701         tgt = builder(env, outfiles[1], infiles[1])[0]
702         s = str(tgt)
703         t = os.path.normcase(test.workpath('1.out'))
704         assert os.path.normcase(s) == t, s
705         tgt.prepare()
706         tgt.build()
707         assert env['CNT'][0] == 2
708         tgts = builder(env, None, infiles[2:4])
709         try:
710             [].extend(collections.UserList())
711         except TypeError:
712             # Old Python version (1.5.2) that can't handle extending
713             # a list with list-like objects.  That means the return
714             # value from the builder call is a real list with Nodes,
715             # and doesn't have a __str__() method that stringifies
716             # the individual elements.  Since we're gong to drop 1.5.2
717             # support anyway, don't bother trying to test for it.
718             pass
719         else:
720             s = list(map(str, tgts))
721             expect = [test.workpath('2.out'), test.workpath('3.out')]
722             expect = list(map(os.path.normcase, expect))
723             assert list(map(os.path.normcase, s)) == expect, s
724         for t in tgts: t.prepare()
725         tgts[0].build()
726         tgts[1].build()
727         assert env['CNT'][0] == 4
728         try:
729             tgt = builder(env, outfiles[4], infiles[4:6])
730         except SCons.Errors.UserError:
731             pass
732         else:
733             assert 0
734         try:
735             # The builder may output more than one target per input file.
736             tgt = builder(env, outfiles[4:6], infiles[4:6])
737         except SCons.Errors.UserError:
738             pass
739         else:
740             assert 0
741         
742         
743     def test_lists(self):
744         """Testing handling lists of targets and source"""
745         def function2(target, source, env, tlist = [outfile, outfile2], **kw):
746             for t in target:
747                 open(str(t), 'w').write("function2\n")
748             for t in tlist:
749                 if not t in list(map(str, target)):
750                     open(t, 'w').write("function2\n")
751             return 1
752
753         env = Environment()
754         builder = SCons.Builder.Builder(action = function2)
755
756         tgts = builder(env, source=[])
757         assert tgts == [], tgts
758
759         tgts = builder(env, target = [outfile, outfile2], source = infile)
760         for t in tgts:
761             t.prepare()
762         try:
763             tgts[0].build()
764         except SCons.Errors.BuildError:
765             pass
766         c = test.read(outfile, 'r')
767         assert c == "function2\n", c
768         c = test.read(outfile2, 'r')
769         assert c == "function2\n", c
770
771         sub1_out = test.workpath('sub1', 'out')
772         sub2_out = test.workpath('sub2', 'out')
773
774         def function3(target, source, env, tlist = [sub1_out, sub2_out]):
775             for t in target:
776                 open(str(t), 'w').write("function3\n")
777             for t in tlist:
778                 if not t in list(map(str, target)):
779                     open(t, 'w').write("function3\n")
780             return 1
781
782         builder = SCons.Builder.Builder(action = function3)
783         tgts = builder(env, target = [sub1_out, sub2_out], source = infile)
784         for t in tgts:
785             t.prepare()
786         try:
787             tgts[0].build()
788         except SCons.Errors.BuildError:
789             pass
790         c = test.read(sub1_out, 'r')
791         assert c == "function3\n", c
792         c = test.read(sub2_out, 'r')
793         assert c == "function3\n", c
794         assert os.path.exists(test.workpath('sub1'))
795         assert os.path.exists(test.workpath('sub2'))
796
797     def test_src_builder(self):
798         """Testing Builders with src_builder"""
799         # These used to be MultiStepBuilder objects until we
800         # eliminated it as a separate class
801         env = Environment()
802         builder1 = SCons.Builder.Builder(action='foo',
803                                          src_suffix='.bar',
804                                          suffix='.foo')
805         builder2 = SCons.Builder.Builder(action=MyAction('act'),
806                                          src_builder = builder1,
807                                          src_suffix = '.foo')
808
809         tgt = builder2(env, source=[])
810         assert tgt == [], tgt
811
812         sources = ['test.bar', 'test2.foo', 'test3.txt', 'test4']
813         tgt = builder2(env, target='baz', source=sources)[0]
814         s = str(tgt)
815         assert s == 'baz', s
816         s = list(map(str, tgt.sources))
817         assert s == ['test.foo', 'test2.foo', 'test3.txt', 'test4.foo'], s
818         s = list(map(str, tgt.sources[0].sources))
819         assert s == ['test.bar'], s
820
821         tgt = builder2(env, None, 'aaa.bar')[0]
822         s = str(tgt)
823         assert s == 'aaa', s
824         s = list(map(str, tgt.sources))
825         assert s == ['aaa.foo'], s
826         s = list(map(str, tgt.sources[0].sources))
827         assert s == ['aaa.bar'], s
828
829         builder3 = SCons.Builder.Builder(action='bld3')
830         assert not builder3.src_builder is builder1.src_builder
831
832         builder4 = SCons.Builder.Builder(action='bld4',
833                                          src_suffix='.i',
834                                          suffix='_wrap.c')
835         builder5 = SCons.Builder.Builder(action=MyAction('act'),
836                                          src_builder=builder4,
837                                          suffix='.obj',
838                                          src_suffix='.c')
839         builder6 = SCons.Builder.Builder(action=MyAction('act'),
840                                          src_builder=builder5,
841                                          suffix='.exe',
842                                          src_suffix='.obj')
843         tgt = builder6(env, 'test', 'test.i')[0]
844         s = str(tgt)
845         assert s == 'test.exe', s
846         s = list(map(str, tgt.sources))
847         assert s == ['test_wrap.obj'], s
848         s = list(map(str, tgt.sources[0].sources))
849         assert s == ['test_wrap.c'], s
850         s = list(map(str, tgt.sources[0].sources[0].sources))
851         assert s == ['test.i'], s
852
853     def test_target_scanner(self):
854         """Testing ability to set target and source scanners through a builder."""
855         global instanced
856         class TestScanner:
857             pass
858         tscan = TestScanner()
859         sscan = TestScanner()
860         env = Environment()
861         builder = SCons.Builder.Builder(target_scanner=tscan,
862                                         source_scanner=sscan,
863                                         action='')
864         tgt = builder(env, target='foo2', source='bar')[0]
865         assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner
866         assert tgt.builder.source_scanner == sscan, tgt.builder.source_scanner
867
868         builder1 = SCons.Builder.Builder(action='foo',
869                                          src_suffix='.bar',
870                                          suffix='.foo')
871         builder2 = SCons.Builder.Builder(action='foo',
872                                          src_builder = builder1,
873                                          target_scanner = tscan,
874                                          source_scanner = tscan)
875         tgt = builder2(env, target='baz2', source='test.bar test2.foo test3.txt')[0]
876         assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner
877         assert tgt.builder.source_scanner == tscan, tgt.builder.source_scanner
878
879     def test_actual_scanner(self):
880         """Test usage of actual Scanner objects."""
881
882         import SCons.Scanner
883
884         def func(self):
885             pass
886         
887         scanner = SCons.Scanner.Base(func, name='fooscan')
888
889         b1 = SCons.Builder.Builder(action='bld', target_scanner=scanner)
890         b2 = SCons.Builder.Builder(action='bld', target_scanner=scanner)
891         b3 = SCons.Builder.Builder(action='bld')
892
893         assert b1 == b2
894         assert b1 != b3
895         
896     def test_src_scanner(slf):
897         """Testing ability to set a source file scanner through a builder."""
898         class TestScanner:
899             def key(self, env):
900                  return 'TestScannerkey'
901             def instance(self, env):
902                  return self
903             def select(self, node):
904                  return self
905             name = 'TestScanner'
906             def __str__(self):
907                 return self.name
908
909         scanner = TestScanner()
910         builder = SCons.Builder.Builder(action='action')
911
912         # With no scanner specified, source_scanner and
913         # backup_source_scanner are None.
914         bar_y = MyNode('bar.y')
915         env1 = Environment()
916         tgt = builder(env1, target='foo1.x', source='bar.y')[0]
917         src = tgt.sources[0]
918         assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
919         assert tgt.builder.source_scanner is None, tgt.builder.source_scanner
920         assert tgt.get_source_scanner(bar_y) is None, tgt.get_source_scanner(bar_y)
921         assert not src.has_builder(), src.has_builder()
922         s = src.get_source_scanner(bar_y)
923         assert isinstance(s, SCons.Util.Null), repr(s)
924
925         # An Environment that has suffix-specified SCANNERS should
926         # provide a source scanner to the target.
927         class EnvTestScanner:
928             def key(self, env):
929                  return '.y'
930             def instance(self, env):
931                  return self
932             name = 'EnvTestScanner'
933             def __str__(self):
934                 return self.name
935             def select(self, node):
936                 return self
937             def path(self, env, dir=None):
938                 return ()
939             def __call__(self, node, env, path):
940                 return []
941         env3 = Environment(SCANNERS = [EnvTestScanner()])
942         env3.scanner = EnvTestScanner() # test env's version of SCANNERS
943         tgt = builder(env3, target='foo2.x', source='bar.y')[0]
944         src = tgt.sources[0]
945         assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
946         assert not tgt.builder.source_scanner, tgt.builder.source_scanner
947         assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
948         assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y)
949         assert not src.has_builder(), src.has_builder()
950         s = src.get_source_scanner(bar_y)
951         assert isinstance(s, SCons.Util.Null), repr(s)
952
953         # Can't simply specify the scanner as a builder argument; it's
954         # global to all invocations of this builder.
955         tgt = builder(env3, target='foo3.x', source='bar.y', source_scanner = scanner)[0]
956         src = tgt.sources[0]
957         assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
958         assert not tgt.builder.source_scanner, tgt.builder.source_scanner
959         assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
960         assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y)
961         assert not src.has_builder(), src.has_builder()
962         s = src.get_source_scanner(bar_y)
963         assert isinstance(s, SCons.Util.Null), s
964
965         # Now use a builder that actually has scanners and ensure that
966         # the target is set accordingly (using the specified scanner
967         # instead of the Environment's scanner)
968         builder = SCons.Builder.Builder(action='action',
969                                         source_scanner=scanner,
970                                         target_scanner=scanner)
971         tgt = builder(env3, target='foo4.x', source='bar.y')[0]
972         src = tgt.sources[0]
973         assert tgt.builder.target_scanner == scanner, tgt.builder.target_scanner
974         assert tgt.builder.source_scanner, tgt.builder.source_scanner
975         assert tgt.builder.source_scanner == scanner, tgt.builder.source_scanner
976         assert str(tgt.builder.source_scanner) == 'TestScanner', str(tgt.builder.source_scanner)
977         assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
978         assert tgt.get_source_scanner(bar_y) == scanner, tgt.get_source_scanner(bar_y)
979         assert str(tgt.get_source_scanner(bar_y)) == 'TestScanner', tgt.get_source_scanner(bar_y)
980         assert not src.has_builder(), src.has_builder()
981         s = src.get_source_scanner(bar_y)
982         assert isinstance(s, SCons.Util.Null), s
983
984
985
986     def test_Builder_API(self):
987         """Test Builder interface.
988
989         Some of this is tested elsewhere in this file, but this is a
990         quick collection of common operations on builders with various
991         forms of component specifications."""
992
993         builder = SCons.Builder.Builder()
994         env = Environment(BUILDERS={'Bld':builder})
995
996         r = builder.get_name(env)
997         assert r == 'Bld', r
998         r = builder.get_prefix(env)
999         assert r == '', r
1000         r = builder.get_suffix(env)
1001         assert r == '', r
1002         r = builder.get_src_suffix(env)
1003         assert r == '', r
1004         r = builder.src_suffixes(env)
1005         assert r == [], r
1006
1007         # src_suffix can be a single string or a list of strings
1008         # src_suffixes() caches its return value, so we use a new
1009         # Builder each time we do any of these tests
1010
1011         bld = SCons.Builder.Builder()
1012         env = Environment(BUILDERS={'Bld':bld})
1013
1014         bld.set_src_suffix('.foo')
1015         r = bld.get_src_suffix(env)
1016         assert r == '.foo', r
1017         r = bld.src_suffixes(env)
1018         assert r == ['.foo'], r
1019
1020         bld = SCons.Builder.Builder()
1021         env = Environment(BUILDERS={'Bld':bld})
1022
1023         bld.set_src_suffix(['.foo', '.bar'])
1024         r = bld.get_src_suffix(env)
1025         assert r == '.foo', r
1026         r = bld.src_suffixes(env)
1027         assert r == ['.foo', '.bar'], r
1028
1029         bld = SCons.Builder.Builder()
1030         env = Environment(BUILDERS={'Bld':bld})
1031
1032         bld.set_src_suffix(['.bar', '.foo'])
1033         r = bld.get_src_suffix(env)
1034         assert r == '.bar', r
1035         r = sorted(bld.src_suffixes(env))
1036         assert r == ['.bar', '.foo'], r
1037
1038         # adjust_suffix normalizes the suffix, adding a `.' if needed
1039
1040         r = builder.adjust_suffix('.foo')
1041         assert r == '.foo', r
1042         r = builder.adjust_suffix('_foo')
1043         assert r == '_foo', r
1044         r = builder.adjust_suffix('$foo')
1045         assert r == '$foo', r
1046         r = builder.adjust_suffix('foo')
1047         assert r == '.foo', r
1048         r = builder.adjust_suffix('f._$oo')
1049         assert r == '.f._$oo', r
1050
1051         # prefix and suffix can be one of:
1052         #   1. a string (adjusted and env variables substituted),
1053         #   2. a function (passed (env,sources), returns suffix string)
1054         #   3. a dict of src_suffix:suffix settings, key==None is
1055         #      default suffix (special case of #2, so adjust_suffix
1056         #      not applied)
1057
1058         builder = SCons.Builder.Builder(prefix='lib', suffix='foo')
1059
1060         env = Environment(BUILDERS={'Bld':builder})
1061         r = builder.get_name(env)
1062         assert r == 'Bld', r
1063         r = builder.get_prefix(env)
1064         assert r == 'lib', r
1065         r = builder.get_suffix(env)
1066         assert r == '.foo', r
1067
1068         mkpref = lambda env,sources: 'Lib'
1069         mksuff = lambda env,sources: '.Foo'
1070         builder = SCons.Builder.Builder(prefix=mkpref, suffix=mksuff)
1071
1072         env = Environment(BUILDERS={'Bld':builder})
1073         r = builder.get_name(env)
1074         assert r == 'Bld', r
1075         r = builder.get_prefix(env)
1076         assert r == 'Lib', r
1077         r = builder.get_suffix(env)
1078         assert r == '.Foo', r
1079
1080         builder = SCons.Builder.Builder(prefix='$PREF', suffix='$SUFF')
1081
1082         env = Environment(BUILDERS={'Bld':builder},PREF="LIB",SUFF=".FOO")
1083         r = builder.get_name(env)
1084         assert r == 'Bld', r
1085         r = builder.get_prefix(env)
1086         assert r == 'LIB', r
1087         r = builder.get_suffix(env)
1088         assert r == '.FOO', r
1089
1090         builder = SCons.Builder.Builder(prefix={None:'A_',
1091                                                 '.C':'E_'},
1092                                         suffix={None:'.B',
1093                                                 '.C':'.D'})
1094
1095         env = Environment(BUILDERS={'Bld':builder})
1096         r = builder.get_name(env)
1097         assert r == 'Bld', r
1098         r = builder.get_prefix(env)
1099         assert r == 'A_', r
1100         r = builder.get_suffix(env)
1101         assert r == '.B', r
1102         r = builder.get_prefix(env, [MyNode('X.C')])
1103         assert r == 'E_', r
1104         r = builder.get_suffix(env, [MyNode('X.C')])
1105         assert r == '.D', r
1106
1107         builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1108         env = Environment(BUILDERS={'Bld':builder})
1109
1110         r = builder.get_name(env)
1111         assert r == 'Bld', r
1112         r = builder.get_prefix(env)
1113         assert r == 'A_', r
1114         r = builder.get_suffix(env)
1115         assert r is None, r
1116         r = builder.get_src_suffix(env)
1117         assert r == '', r
1118         r = builder.src_suffixes(env)
1119         assert r == [], r
1120
1121         # Builder actions can be a string, a list, or a dictionary
1122         # whose keys are the source suffix.  The add_action()
1123         # specifies a new source suffix/action binding.
1124
1125         builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1126         env = Environment(BUILDERS={'Bld':builder})
1127         builder.add_action('.src_sfx1', 'FOO')
1128
1129         r = builder.get_name(env)
1130         assert r == 'Bld', r
1131         r = builder.get_prefix(env)
1132         assert r == 'A_', r
1133         r = builder.get_suffix(env)
1134         assert r is None, r
1135         r = builder.get_suffix(env, [MyNode('X.src_sfx1')])
1136         assert r is None, r
1137         r = builder.get_src_suffix(env)
1138         assert r == '.src_sfx1', r
1139         r = builder.src_suffixes(env)
1140         assert r == ['.src_sfx1'], r
1141
1142         builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1143         env = Environment(BUILDERS={'Bld':builder})
1144         builder.add_action('.src_sfx1', 'FOO')
1145         builder.add_action('.src_sfx2', 'BAR')
1146
1147         r = builder.get_name(env)
1148         assert r == 'Bld', r
1149         r = builder.get_prefix(env)
1150         assert r == 'A_', r
1151         r = builder.get_suffix(env)
1152         assert r is None, r
1153         r = builder.get_src_suffix(env)
1154         assert r == '.src_sfx1', r
1155         r = sorted(builder.src_suffixes(env))
1156         assert r == ['.src_sfx1', '.src_sfx2'], r
1157
1158
1159     def test_Builder_Args(self):
1160         """Testing passing extra args to a builder."""
1161         def buildFunc(target, source, env, s=self):
1162             s.foo=env['foo']
1163             s.bar=env['bar']
1164             assert env['CC'] == 'mycc'
1165
1166         env=Environment(CC='cc')
1167
1168         builder = SCons.Builder.Builder(action=buildFunc)
1169         tgt = builder(env, target='foo', source='bar', foo=1, bar=2, CC='mycc')[0]
1170         tgt.build()
1171         assert self.foo == 1, self.foo
1172         assert self.bar == 2, self.bar
1173
1174     def test_emitter(self):
1175         """Test emitter functions."""
1176         def emit(target, source, env):
1177             foo = env.get('foo', 0)
1178             bar = env.get('bar', 0)
1179             for t in target:
1180                 assert isinstance(t, MyNode)
1181                 assert t.has_builder()
1182             for s in source:
1183                 assert isinstance(s, MyNode)
1184             if foo:
1185                 target.append("bar%d"%foo)
1186             if bar:
1187                 source.append("baz")
1188             return ( target, source )
1189
1190         env = Environment()
1191         builder = SCons.Builder.Builder(action='foo',
1192                                         emitter=emit,
1193                                         target_factory=MyNode,
1194                                         source_factory=MyNode)
1195         tgt = builder(env, target='foo2', source='bar')[0]
1196         assert str(tgt) == 'foo2', str(tgt)
1197         assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
1198
1199         tgt = builder(env, target='foo3', source='bar', foo=1)
1200         assert len(tgt) == 2, len(tgt)
1201         assert 'foo3' in list(map(str, tgt)), list(map(str, tgt))
1202         assert 'bar1' in list(map(str, tgt)), list(map(str, tgt))
1203
1204         tgt = builder(env, target='foo4', source='bar', bar=1)[0]
1205         assert str(tgt) == 'foo4', str(tgt)
1206         assert len(tgt.sources) == 2, len(tgt.sources)
1207         assert 'baz' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1208         assert 'bar' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1209
1210         env2=Environment(FOO=emit)
1211         builder2=SCons.Builder.Builder(action='foo',
1212                                        emitter="$FOO",
1213                                        target_factory=MyNode,
1214                                        source_factory=MyNode)
1215
1216         builder2a=SCons.Builder.Builder(action='foo',
1217                                         emitter="$FOO",
1218                                         target_factory=MyNode,
1219                                         source_factory=MyNode)
1220
1221         assert builder2 == builder2a, repr(builder2.__dict__) + "\n" + repr(builder2a.__dict__)
1222
1223         tgt = builder2(env2, target='foo5', source='bar')[0]
1224         assert str(tgt) == 'foo5', str(tgt)
1225         assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
1226
1227         tgt = builder2(env2, target='foo6', source='bar', foo=2)
1228         assert len(tgt) == 2, len(tgt)
1229         assert 'foo6' in list(map(str, tgt)), list(map(str, tgt))
1230         assert 'bar2' in list(map(str, tgt)), list(map(str, tgt))
1231
1232         tgt = builder2(env2, target='foo7', source='bar', bar=1)[0]
1233         assert str(tgt) == 'foo7', str(tgt)
1234         assert len(tgt.sources) == 2, len(tgt.sources)
1235         assert 'baz' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1236         assert 'bar' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1237
1238     def test_emitter_preserve_builder(self):
1239         """Test an emitter not overwriting a newly-set builder"""
1240         env = Environment()
1241
1242         new_builder = SCons.Builder.Builder(action='new')
1243         node = MyNode('foo8')
1244         new_node = MyNode('foo8.new')
1245
1246         def emit(target, source, env, nb=new_builder, nn=new_node):
1247             for t in target:
1248                 t.builder = nb
1249             return [nn], source
1250             
1251         builder=SCons.Builder.Builder(action='foo',
1252                                       emitter=emit,
1253                                       target_factory=MyNode,
1254                                       source_factory=MyNode)
1255         tgt = builder(env, target=node, source='bar')[0]
1256         assert tgt is new_node, tgt
1257         assert tgt.builder is builder, tgt.builder
1258         assert node.builder is new_builder, node.builder
1259
1260     def test_emitter_suffix_map(self):
1261         """Test mapping file suffixes to emitter functions"""
1262         env = Environment()
1263
1264         def emit4a(target, source, env):
1265             source = list(map(str, source))
1266             target = ['emit4a-' + x[:-3] for x in source]
1267             return (target, source)
1268         def emit4b(target, source, env):
1269             source = list(map(str, source))
1270             target = ['emit4b-' + x[:-3] for x in source]
1271             return (target, source)
1272
1273         builder = SCons.Builder.Builder(action='foo',
1274                                         emitter={'.4a':emit4a,
1275                                                  '.4b':emit4b},
1276                                         target_factory=MyNode,
1277                                         source_factory=MyNode)
1278         tgt = builder(env, None, source='aaa.4a')[0]
1279         assert str(tgt) == 'emit4a-aaa', str(tgt)
1280         tgt = builder(env, None, source='bbb.4b')[0]
1281         assert str(tgt) == 'emit4b-bbb', str(tgt)
1282         tgt = builder(env, None, source='ccc.4c')[0]
1283         assert str(tgt) == 'ccc', str(tgt)
1284
1285         def emit4c(target, source, env):
1286             source = list(map(str, source))
1287             target = ['emit4c-' + x[:-3] for x in source]
1288             return (target, source)
1289
1290         builder.add_emitter('.4c', emit4c)
1291         tgt = builder(env, None, source='ccc.4c')[0]
1292         assert str(tgt) == 'emit4c-ccc', str(tgt)
1293
1294     def test_emitter_function_list(self):
1295         """Test lists of emitter functions"""
1296         env = Environment()
1297
1298         def emit1a(target, source, env):
1299             source = list(map(str, source))
1300             target = target + ['emit1a-' + x[:-2] for x in source]
1301             return (target, source)
1302         def emit1b(target, source, env):
1303             source = list(map(str, source))
1304             target = target + ['emit1b-' + x[:-2] for x in source]
1305             return (target, source)
1306         builder1 = SCons.Builder.Builder(action='foo',
1307                                          emitter=[emit1a, emit1b],
1308                                          node_factory=MyNode)
1309
1310         tgts = builder1(env, target='target-1', source='aaa.1')
1311         tgts = list(map(str, tgts))
1312         assert tgts == ['target-1', 'emit1a-aaa', 'emit1b-aaa'], tgts
1313
1314         # Test a list of emitter functions through the environment.
1315         def emit2a(target, source, env):
1316             source = list(map(str, source))
1317             target = target + ['emit2a-' + x[:-2] for x in source]
1318             return (target, source)
1319         def emit2b(target, source, env):
1320             source = list(map(str, source))
1321             target = target + ['emit2b-' + x[:-2] for x in source]
1322             return (target, source)
1323         builder2 = SCons.Builder.Builder(action='foo',
1324                                          emitter='$EMITTERLIST',
1325                                          node_factory=MyNode)
1326                                          
1327         env = Environment(EMITTERLIST = [emit2a, emit2b])
1328
1329         tgts = builder2(env, target='target-2', source='aaa.2')
1330         tgts = list(map(str, tgts))
1331         assert tgts == ['target-2', 'emit2a-aaa', 'emit2b-aaa'], tgts
1332
1333     def test_emitter_TARGET_SOURCE(self):
1334         """Test use of $TARGET and $SOURCE in emitter results"""
1335
1336         env = SCons.Environment.Environment()
1337
1338         def emit(target, source, env):
1339             return (target + ['${SOURCE}.s1', '${TARGET}.t1'],
1340                     source + ['${TARGET}.t2', '${SOURCE}.s2'])
1341
1342         builder = SCons.Builder.Builder(action='foo',
1343                                         emitter = emit,
1344                                         node_factory = MyNode)
1345
1346         targets = builder(env, target = 'TTT', source ='SSS')
1347         sources = targets[0].sources
1348         targets = list(map(str, targets))
1349         sources = list(map(str, sources))
1350         assert targets == ['TTT', 'SSS.s1', 'TTT.t1'], targets
1351         assert sources == ['SSS', 'TTT.t2', 'SSS.s2'], targets
1352
1353     def test_no_target(self):
1354         """Test deducing the target from the source."""
1355
1356         env = Environment()
1357         b = SCons.Builder.Builder(action='foo', suffix='.o')
1358
1359         tgt = b(env, None, 'aaa')[0]
1360         assert str(tgt) == 'aaa.o', str(tgt)
1361         assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1362         assert str(tgt.sources[0]) == 'aaa', list(map(str, tgt.sources))
1363
1364         tgt = b(env, None, 'bbb.c')[0]
1365         assert str(tgt) == 'bbb.o', str(tgt)
1366         assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1367         assert str(tgt.sources[0]) == 'bbb.c', list(map(str, tgt.sources))
1368
1369         tgt = b(env, None, 'ccc.x.c')[0]
1370         assert str(tgt) == 'ccc.x.o', str(tgt)
1371         assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1372         assert str(tgt.sources[0]) == 'ccc.x.c', list(map(str, tgt.sources))
1373
1374         tgt = b(env, None, ['d0.c', 'd1.c'])[0]
1375         assert str(tgt) == 'd0.o', str(tgt)
1376         assert len(tgt.sources) == 2,  list(map(str, tgt.sources))
1377         assert str(tgt.sources[0]) == 'd0.c', list(map(str, tgt.sources))
1378         assert str(tgt.sources[1]) == 'd1.c', list(map(str, tgt.sources))
1379
1380         tgt = b(env, target = None, source='eee')[0]
1381         assert str(tgt) == 'eee.o', str(tgt)
1382         assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1383         assert str(tgt.sources[0]) == 'eee', list(map(str, tgt.sources))
1384
1385         tgt = b(env, target = None, source='fff.c')[0]
1386         assert str(tgt) == 'fff.o', str(tgt)
1387         assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1388         assert str(tgt.sources[0]) == 'fff.c', list(map(str, tgt.sources))
1389
1390         tgt = b(env, target = None, source='ggg.x.c')[0]
1391         assert str(tgt) == 'ggg.x.o', str(tgt)
1392         assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1393         assert str(tgt.sources[0]) == 'ggg.x.c', list(map(str, tgt.sources))
1394
1395         tgt = b(env, target = None, source=['h0.c', 'h1.c'])[0]
1396         assert str(tgt) == 'h0.o', str(tgt)
1397         assert len(tgt.sources) == 2,  list(map(str, tgt.sources))
1398         assert str(tgt.sources[0]) == 'h0.c', list(map(str, tgt.sources))
1399         assert str(tgt.sources[1]) == 'h1.c', list(map(str, tgt.sources))
1400
1401         w = b(env, target='i0.w', source=['i0.x'])[0]
1402         y = b(env, target='i1.y', source=['i1.z'])[0]
1403         tgt = b(env, None, source=[w, y])[0]
1404         assert str(tgt) == 'i0.o', str(tgt)
1405         assert len(tgt.sources) == 2, list(map(str, tgt.sources))
1406         assert str(tgt.sources[0]) == 'i0.w', list(map(str, tgt.sources))
1407         assert str(tgt.sources[1]) == 'i1.y', list(map(str, tgt.sources))
1408
1409     def test_get_name(self):
1410         """Test getting name of builder.
1411
1412         Each type of builder should return its environment-specific
1413         name when queried appropriately.  """
1414
1415         b1 = SCons.Builder.Builder(action='foo', suffix='.o')
1416         b2 = SCons.Builder.Builder(action='foo', suffix='.c')
1417         b3 = SCons.Builder.Builder(action='bar', src_suffix = '.foo',
1418                                                  src_builder = b1)
1419         b4 = SCons.Builder.Builder(action={})
1420         b5 = SCons.Builder.Builder(action='foo', name='builder5')
1421         b6 = SCons.Builder.Builder(action='foo')
1422         assert isinstance(b4, SCons.Builder.CompositeBuilder)
1423         assert isinstance(b4.action, SCons.Action.CommandGeneratorAction)
1424         
1425         env = Environment(BUILDERS={'bldr1': b1,
1426                                     'bldr2': b2,
1427                                     'bldr3': b3,
1428                                     'bldr4': b4})
1429         env2 = Environment(BUILDERS={'B1': b1,
1430                                      'B2': b2,
1431                                      'B3': b3,
1432                                      'B4': b4})
1433         # With no name, get_name will return the class.  Allow
1434         # for caching...
1435         b6_names = [
1436             'SCons.Builder.BuilderBase',
1437             "<class 'SCons.Builder.BuilderBase'>",
1438             'SCons.Memoize.BuilderBase',
1439             "<class 'SCons.Memoize.BuilderBase'>",
1440         ]
1441
1442         assert b1.get_name(env) == 'bldr1', b1.get_name(env)
1443         assert b2.get_name(env) == 'bldr2', b2.get_name(env)
1444         assert b3.get_name(env) == 'bldr3', b3.get_name(env)
1445         assert b4.get_name(env) == 'bldr4', b4.get_name(env)
1446         assert b5.get_name(env) == 'builder5', b5.get_name(env)
1447         assert b6.get_name(env) in b6_names, b6.get_name(env)
1448
1449         assert b1.get_name(env2) == 'B1', b1.get_name(env2)
1450         assert b2.get_name(env2) == 'B2', b2.get_name(env2)
1451         assert b3.get_name(env2) == 'B3', b3.get_name(env2)
1452         assert b4.get_name(env2) == 'B4', b4.get_name(env2)
1453         assert b5.get_name(env2) == 'builder5', b5.get_name(env2)
1454         assert b6.get_name(env2) in b6_names, b6.get_name(env2)
1455
1456         assert b5.get_name(None) == 'builder5', b5.get_name(None)
1457         assert b6.get_name(None) in b6_names, b6.get_name(None)
1458
1459         # This test worked before adding batch builders, but we must now
1460         # be able to disambiguate a CompositeAction into a more specific
1461         # action based on file suffix at call time.  Leave this commented
1462         # out (for now) in case this reflects a real-world use case that
1463         # we must accomodate and we want to resurrect this test.
1464         #tgt = b4(env, target = 'moo', source='cow')
1465         #assert tgt[0].builder.get_name(env) == 'bldr4'
1466
1467 class CompositeBuilderTestCase(unittest.TestCase):
1468
1469     def setUp(self):
1470         def func_action(target, source, env):
1471             return 0
1472
1473         builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1474                                                  '.bar' : func_action})
1475
1476         self.func_action = func_action
1477         self.builder = builder
1478
1479     def test___init__(self):
1480         """Test CompositeBuilder creation"""
1481         env = Environment()
1482         builder = SCons.Builder.Builder(action={})
1483
1484         tgt = builder(env, source=[])
1485         assert tgt == [], tgt
1486         
1487         assert isinstance(builder, SCons.Builder.CompositeBuilder)
1488         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1489
1490     def test_target_action(self):
1491         """Test CompositeBuilder setting of target builder actions"""
1492         env = Environment()
1493         builder = self.builder
1494
1495         tgt = builder(env, target='test1', source='test1.foo')[0]
1496         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1497         assert tgt.builder.action is builder.action
1498
1499         tgt = builder(env, target='test2', source='test1.bar')[0]
1500         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1501         assert tgt.builder.action is builder.action
1502
1503     def test_multiple_suffix_error(self):
1504         """Test the CompositeBuilder multiple-source-suffix error"""
1505         env = Environment()
1506         builder = self.builder
1507
1508         flag = 0
1509         try:
1510             builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0]
1511         except SCons.Errors.UserError, e:
1512             flag = 1
1513         assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1514         expect = "While building `['test3']' from `test1.foo': Cannot build multiple sources with different extensions: .bar, .foo"
1515         assert str(e) == expect, e
1516
1517     def test_source_ext_match(self):
1518         """Test the CompositeBuilder source_ext_match argument"""
1519         env = Environment()
1520         func_action = self.func_action
1521         builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1522                                                  '.bar' : func_action},
1523                                         source_ext_match = None)
1524
1525         tgt = builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0]
1526         tgt.build()
1527
1528     def test_suffix_variable(self):
1529         """Test CompositeBuilder defining action suffixes through a variable"""
1530         env = Environment(BAR_SUFFIX = '.BAR2', FOO_SUFFIX = '.FOO2')
1531         func_action = self.func_action
1532         builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1533                                                  '.bar' : func_action,
1534                                                  '$BAR_SUFFIX' : func_action,
1535                                                  '$FOO_SUFFIX' : func_action })
1536
1537         tgt = builder(env, target='test4', source=['test4.BAR2'])[0]
1538         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1539         try:
1540             tgt.build()
1541             flag = 1
1542         except SCons.Errors.UserError, e:
1543             print e
1544             flag = 0
1545         assert flag, "It should be possible to define actions in composite builders using variables."
1546         env['FOO_SUFFIX'] = '.BAR2'
1547         builder.add_action('$NEW_SUFFIX', func_action)
1548         flag = 0
1549         try:
1550             builder(env, target='test5', source=['test5.BAR2'])[0]
1551         except SCons.Errors.UserError:
1552             flag = 1
1553         assert flag, "UserError should be thrown when we call a builder with ambigous suffixes."
1554
1555     def test_src_builder(self):
1556         """Test CompositeBuilder's use of a src_builder"""
1557         env = Environment()
1558
1559         foo_bld = SCons.Builder.Builder(action = 'a-foo',
1560                                         src_suffix = '.ina',
1561                                         suffix = '.foo')
1562         assert isinstance(foo_bld, SCons.Builder.BuilderBase)
1563         builder = SCons.Builder.Builder(action = { '.foo' : 'foo',
1564                                                    '.bar' : 'bar' },
1565                                         src_builder = foo_bld)
1566         assert isinstance(builder, SCons.Builder.CompositeBuilder)
1567         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1568
1569         tgt = builder(env, target='t1', source='t1a.ina t1b.ina')[0]
1570         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1571
1572         tgt = builder(env, target='t2', source='t2a.foo t2b.ina')[0]
1573         assert isinstance(tgt.builder, SCons.Builder.BuilderBase), tgt.builder.__dict__
1574
1575         bar_bld = SCons.Builder.Builder(action = 'a-bar',
1576                                         src_suffix = '.inb',
1577                                         suffix = '.bar')
1578         assert isinstance(bar_bld, SCons.Builder.BuilderBase)
1579         builder = SCons.Builder.Builder(action = { '.foo' : 'foo'},
1580                                         src_builder = [foo_bld, bar_bld])
1581         assert isinstance(builder, SCons.Builder.CompositeBuilder)
1582         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1583
1584         builder.add_action('.bar', 'bar')
1585
1586         tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')[0]
1587         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1588
1589         tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')[0]
1590         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1591
1592         flag = 0
1593         try:
1594             builder(env, target='t5', source=['test5a.foo', 'test5b.inb'])[0]
1595         except SCons.Errors.UserError, e:
1596             flag = 1
1597         assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1598         expect = "While building `['t5']' from `test5b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
1599         assert str(e) == expect, e
1600
1601         flag = 0
1602         try:
1603             builder(env, target='t6', source=['test6a.bar', 'test6b.ina'])[0]
1604         except SCons.Errors.UserError, e:
1605             flag = 1
1606         assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1607         expect = "While building `['t6']' from `test6b.foo': Cannot build multiple sources with different extensions: .bar, .foo"
1608         assert str(e) == expect, e
1609
1610         flag = 0
1611         try:
1612             builder(env, target='t4', source=['test4a.ina', 'test4b.inb'])[0]
1613         except SCons.Errors.UserError, e:
1614             flag = 1
1615         assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1616         expect = "While building `['t4']' from `test4b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
1617         assert str(e) == expect, e
1618
1619         flag = 0
1620         try:
1621             builder(env, target='t7', source=[env.fs.File('test7')])[0]
1622         except SCons.Errors.UserError, e:
1623             flag = 1
1624         assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1625         expect = "While building `['t7']': Cannot deduce file extension from source files: ['test7']"
1626         assert str(e) == expect, e
1627
1628         flag = 0
1629         try:
1630             builder(env, target='t8', source=['test8.unknown'])[0]
1631         except SCons.Errors.UserError, e:
1632             flag = 1
1633         assert flag, "UserError should be thrown when we call a builder target with an unknown suffix."
1634         expect = "While building `['t8']' from `['test8.unknown']': Don't know how to build from a source file with suffix `.unknown'.  Expected a suffix in this list: ['.foo', '.bar']."
1635         assert str(e) == expect, e
1636
1637 if __name__ == "__main__":
1638     suite = unittest.TestSuite()
1639     tclasses = [
1640         BuilderTestCase,
1641         CompositeBuilderTestCase
1642     ]
1643     for tclass in tclasses:
1644         names = unittest.getTestCaseNames(tclass, 'test_')
1645         suite.addTests(list(map(tclass, names)))
1646     if not unittest.TextTestRunner().run(suite).wasSuccessful():
1647         sys.exit(1)
1648
1649 # Local Variables:
1650 # tab-width:4
1651 # indent-tabs-mode:nil
1652 # End:
1653 # vim: set expandtab tabstop=4 shiftwidth=4: