Raise an error if a builder is called multiple times for a given target, unless the...
[scons.git] / src / engine / SCons / BuilderTests.py
1 #
2 # Copyright (c) 2001, 2002 Steven Knight
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 # Define a null function for use as a builder action.
27 # Where this is defined in the file seems to affect its
28 # byte-code contents, so try to minimize changes by
29 # defining it here, before we even import anything.
30 def Func():
31     pass
32
33 import os.path
34 import sys
35 import types
36 import unittest
37
38 import TestCmd
39 import SCons.Builder
40 import SCons.Errors
41 import SCons.Node.FS
42
43 # Initial setup of the common environment for all tests,
44 # a temporary working directory containing a
45 # script for writing arguments to an output file.
46 #
47 # We don't do this as a setUp() method because it's
48 # unnecessary to create a separate directory and script
49 # for each test, they can just use the one.
50 test = TestCmd.TestCmd(workdir = '')
51
52 test.write('act.py', """import os, string, sys
53 f = open(sys.argv[1], 'w')
54 f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n")
55 try:
56     if sys.argv[3]:
57         f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n")
58 except:
59     pass
60 f.close()
61 sys.exit(0)
62 """)
63
64 act_py = test.workpath('act.py')
65 outfile = test.workpath('outfile')
66 outfile2 = test.workpath('outfile2')
67
68 show_string = None
69 env_scanner = None
70 count = 0
71
72 class Environment:
73     def __init__(self, **kw):
74         self.d = {}
75         for k, v in kw.items():
76             self.d[k] = v
77     def subst(self, s):
78         try:
79             if s[0] == '$':
80                 return self.d.get(s[1:], '')
81         except IndexError:
82             pass
83         return self.d.get(s, s)
84     def get_scanner(self, ext):
85         return env_scanner
86     def Dictionary(self):
87         return {}
88     def autogenerate(self, dir=''):
89         return {}
90     def __getitem__(self, item):
91         return self.d[item]
92     def has_key(self, item):
93         return self.d.has_key(item)
94     
95 env = Environment()
96
97 class BuilderTestCase(unittest.TestCase):
98
99     def test__call__(self):
100         """Test calling a builder to establish source dependencies
101         """
102         class Node:
103             def __init__(self, name):
104                 self.name = name
105                 self.sources = []
106                 self.builder = None
107             def __str__(self):
108                 return self.name
109             def builder_set(self, builder):
110                 self.builder = builder
111             def env_set(self, env, safe=0):
112                 self.env = env
113             def add_source(self, source):
114                 self.sources.extend(source)
115             def scanner_key(self):
116                 return self.name
117         builder = SCons.Builder.Builder(name="builder", action="foo", node_factory=Node)
118
119         n1 = Node("n1");
120         n2 = Node("n2");
121         builder(env, target = n1, source = n2)
122         assert n1.env == env
123         assert n1.builder == builder
124         assert n1.sources == [n2]
125         assert not hasattr(n2, 'env')
126
127         target = builder(env, target = 'n3', source = 'n4')
128         assert target.name == 'n3'
129         assert target.sources[0].name == 'n4'
130
131         targets = builder(env, target = 'n4 n5', source = ['n6 n7'])
132         assert targets[0].name == 'n4'
133         assert targets[0].sources[0].name == 'n6 n7'
134         assert targets[1].name == 'n5'
135         assert targets[1].sources[0].name == 'n6 n7'
136
137         target = builder(env, target = ['n8 n9'], source = 'n10 n11')
138         assert target.name == 'n8 n9'
139         assert target.sources[0].name == 'n10'
140         assert target.sources[1].name == 'n11'
141
142         if not hasattr(types, 'UnicodeType'):
143             uni = str
144         else:
145             uni = unicode
146
147         targets = builder(env, target = uni('n12 n13'),
148                           source = [uni('n14 n15')])
149         assert targets[0].name == uni('n12')
150         assert targets[0].sources[0].name == uni('n14 n15')
151         assert targets[1].name == uni('n13')
152         assert targets[1].sources[0].name == uni('n14 n15')
153
154         target = builder(env, target = [uni('n16 n17')],
155                          source = uni('n18 n19'))
156         assert target.name == uni('n16 n17')
157         assert target.sources[0].name == uni('n18')
158         assert target.sources[1].name == uni('n19')
159
160     def test_noname(self):
161         """Test error reporting for missing name
162
163         Verify that the Builder constructor gives an error message if the
164         name is missing.
165         """
166         try:
167             b = SCons.Builder.Builder()
168         except SCons.Errors.UserError:
169             pass
170         else:
171             assert 0
172
173     def test_action(self):
174         """Test Builder creation
175
176         Verify that we can retrieve the supplied action attribute.
177         """
178         builder = SCons.Builder.Builder(name="builder", action="foo")
179         assert builder.action.cmd_list == ["foo"]
180
181     def test_generator(self):
182         """Test Builder creation given a generator function."""
183
184         def generator():
185             pass
186
187         builder = SCons.Builder.Builder(name="builder", generator=generator)
188         assert builder.action.generator == generator
189
190     def test_cmp(self):
191         """Test simple comparisons of Builder objects
192         """
193         b1 = SCons.Builder.Builder(name="b1", src_suffix = '.o')
194         b2 = SCons.Builder.Builder(name="b1", src_suffix = '.o')
195         assert b1 == b2
196         b3 = SCons.Builder.Builder(name="b3", src_suffix = '.x')
197         assert b1 != b3
198         assert b2 != b3
199
200     def test_execute(self):
201         """Test execution of simple Builder objects
202         
203         One Builder is a string that executes an external command,
204         one is an internal Python function, one is a list
205         containing one of each.
206         """
207
208         def MyBuilder(**kw):
209             builder = apply(SCons.Builder.Builder, (), kw)
210             def no_show(str):
211                 pass
212             builder.action.show = no_show
213             return builder
214
215         python = sys.executable
216
217         cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile)
218
219         builder = MyBuilder(action = cmd1, name = "cmd1")
220         r = builder.execute()
221         assert r == 0
222         c = test.read(outfile, 'r')
223         assert c == "act.py: 'xyzzy'\n", c
224
225         cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
226
227         builder = MyBuilder(action = cmd2, name = "cmd2")
228         r = builder.execute(target = 'foo')
229         assert r == 0
230         c = test.read(outfile, 'r')
231         assert c == "act.py: 'foo'\n", c
232
233         cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
234
235         builder = MyBuilder(action = cmd3, name = "cmd3")
236         r = builder.execute(target = ['aaa', 'bbb'])
237         assert r == 0
238         c = test.read(outfile, 'r')
239         assert c == "act.py: 'aaa' 'bbb'\n", c
240
241         cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
242
243         builder = MyBuilder(action = cmd4, name = "cmd4")
244         r = builder.execute(source = ['one', 'two'])
245         assert r == 0
246         c = test.read(outfile, 'r')
247         assert c == "act.py: 'one' 'two'\n", c
248
249         cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile)
250
251         builder = MyBuilder(action = cmd4, name = "cmd4")
252         r = builder.execute(source = ['three', 'four', 'five'])
253         assert r == 0
254         c = test.read(outfile, 'r')
255         assert c == "act.py: 'three' 'four'\n", c
256
257         cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile)
258
259         builder = MyBuilder(action = cmd5, name = "cmd5")
260         r = builder.execute(target = 'out5', env = {'ENV' : {'XYZZY' : 'xyzzy'}})
261         assert r == 0
262         c = test.read(outfile, 'r')
263         assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c
264
265         class Obj:
266             def __init__(self, str):
267                 self._str = str
268             def __str__(self):
269                 return self._str
270
271         cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile)
272
273         builder = MyBuilder(action = cmd6, name = "cmd6")
274         r = builder.execute(target = [Obj('111'), Obj('222')],
275                             source = [Obj('333'), Obj('444'), Obj('555')])
276         assert r == 0
277         c = test.read(outfile, 'r')
278         assert c == "act.py: '222' '111' '333' '444'\n", c
279
280         cmd7 = '%s %s %s one\n\n%s %s %s two' % (python, act_py, outfile,
281                                                  python, act_py, outfile)
282         expect7 = '%s %s %s one\n%s %s %s two\n' % (python, act_py, outfile,
283                                                     python, act_py, outfile)
284
285         builder = MyBuilder(action = cmd7, name = "cmd7")
286
287         global show_string
288         show_string = ""
289         def my_show(string):
290             global show_string
291             show_string = show_string + string + "\n"
292         for action in builder.action.list:
293             action.show = my_show
294
295         r = builder.execute()
296         assert r == 0
297         assert show_string == expect7, show_string
298
299         global count
300         count = 0
301         def function1(**kw):
302             global count
303             count = count + 1
304             if not type(kw['target']) is type([]):
305                 kw['target'] = [ kw['target'] ]
306             for t in kw['target']:
307                 open(t, 'w').write("function1\n")
308             return 1
309
310         builder = MyBuilder(action = function1, name = "function1")
311         try:
312             r = builder.execute(target = [outfile, outfile2])
313         except SCons.Errors.BuildError:
314             pass
315         assert r == 1
316         assert count == 1
317         c = test.read(outfile, 'r')
318         assert c == "function1\n", c
319         c = test.read(outfile2, 'r')
320         assert c == "function1\n", c
321
322         class class1a:
323             def __init__(self, **kw):
324                 open(kw['out'], 'w').write("class1a\n")
325
326         builder = MyBuilder(action = class1a, name = "class1a")
327         r = builder.execute(out = outfile)
328         assert r.__class__ == class1a
329         c = test.read(outfile, 'r')
330         assert c == "class1a\n", c
331
332         class class1b:
333             def __call__(self, **kw):
334                 open(kw['out'], 'w').write("class1b\n")
335                 return 2
336
337         builder = MyBuilder(action = class1b(), name = "class1b")
338         r = builder.execute(out = outfile)
339         assert r == 2
340         c = test.read(outfile, 'r')
341         assert c == "class1b\n", c
342
343         cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile)
344
345         def function2(**kw):
346             open(kw['out'], 'a').write("function2\n")
347             return 0
348
349         class class2a:
350             def __call__(self, **kw):
351                 open(kw['out'], 'a').write("class2a\n")
352                 return 0
353
354         class class2b:
355             def __init__(self, **kw):
356                 open(kw['out'], 'a').write("class2b\n")
357
358         builder = MyBuilder(action = SCons.Action.ListAction([cmd2, function2, class2a(), class2b]), name = "clist")
359         r = builder.execute(out = outfile)
360         assert r.__class__ == class2b
361         c = test.read(outfile, 'r')
362         assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
363
364         if os.name == 'nt':
365             # NT treats execs of directories and non-executable files
366             # as "file not found" errors
367             expect_nonexistent = 1
368             expect_nonexecutable = 1
369         else:
370             expect_nonexistent = 127
371             expect_nonexecutable = 126
372
373         # Test that a nonexistent command returns 127
374         builder = MyBuilder(action = python + "_XyZzY_", name="badcmd")
375         r = builder.execute(out = outfile)
376         assert r == expect_nonexistent, "r == %d" % r
377
378         # Test that trying to execute a directory returns 126
379         dir, tail = os.path.split(python)
380         builder = MyBuilder(action = dir, name = "dir")
381         r = builder.execute(out = outfile)
382         assert r == expect_nonexecutable, "r == %d" % r
383
384         # Test that trying to execute a non-executable file returns 126
385         builder = MyBuilder(action = outfile, name = "badfile")
386         r = builder.execute(out = outfile)
387         assert r == expect_nonexecutable, "r == %d" % r
388
389     def test_get_contents(self):
390         """Test returning the signature contents of a Builder
391         """
392
393         b1 = SCons.Builder.Builder(name = "b1", action = "foo")
394         contents = b1.get_contents()
395         assert contents == "foo", contents
396
397         b2 = SCons.Builder.Builder(name = "b2", action = Func)
398         contents = b2.get_contents()
399         assert contents == "\177\036\000\177\037\000d\000\000S", repr(contents)
400
401         b3 = SCons.Builder.Builder(name = "b3", action = SCons.Action.ListAction(["foo", Func, "bar"]))
402         contents = b3.get_contents()
403         assert contents == "foo\177\036\000\177\037\000d\000\000Sbar", repr(contents)
404
405     def test_node_factory(self):
406         """Test a Builder that creates nodes of a specified class
407         """
408         class Foo:
409             pass
410         def FooFactory(target):
411             global Foo
412             return Foo(target)
413         builder = SCons.Builder.Builder(name = "builder", node_factory = FooFactory)
414         assert builder.target_factory is FooFactory
415         assert builder.source_factory is FooFactory
416
417     def test_target_factory(self):
418         """Test a Builder that creates target nodes of a specified class
419         """
420         class Foo:
421             pass
422         def FooFactory(target):
423             global Foo
424             return Foo(target)
425         builder = SCons.Builder.Builder(name = "builder", target_factory = FooFactory)
426         assert builder.target_factory is FooFactory
427         assert not builder.source_factory is FooFactory
428
429     def test_source_factory(self):
430         """Test a Builder that creates source nodes of a specified class
431         """
432         class Foo:
433             pass
434         def FooFactory(source):
435             global Foo
436             return Foo(source)
437         builder = SCons.Builder.Builder(name = "builder", source_factory = FooFactory)
438         assert not builder.target_factory is FooFactory
439         assert builder.source_factory is FooFactory
440
441     def test_prefix(self):
442         """Test Builder creation with a specified target prefix
443
444         Make sure that there is no '.' separator appended.
445         """
446         builder = SCons.Builder.Builder(name = "builder", prefix = 'lib.')
447         assert builder.get_prefix(env,{}) == 'lib.'
448         builder = SCons.Builder.Builder(name = "builder", prefix = 'lib')
449         assert builder.get_prefix(env,{}) == 'lib'
450         tgt = builder(env, target = 'tgt1', source = 'src1')
451         assert tgt.path == 'libtgt1', \
452                 "Target has unexpected name: %s" % tgt.path
453         tgts = builder(env, target = 'tgt2a tgt2b', source = 'src2')
454         assert tgts[0].path == 'libtgt2a', \
455                 "Target has unexpected name: %s" % tgts[0].path
456         assert tgts[1].path == 'libtgt2b', \
457                 "Target has unexpected name: %s" % tgts[1].path
458
459     def test_src_suffix(self):
460         """Test Builder creation with a specified source file suffix
461         
462         Make sure that the '.' separator is appended to the
463         beginning if it isn't already present.
464         """
465         env = Environment(XSUFFIX = '.x', YSUFFIX = '.y')
466
467         b1 = SCons.Builder.Builder(name = "builder", src_suffix = '.c')
468         assert b1.src_suffixes(env,{}) == ['.c'], b1.src_suffixes(env,{})
469
470         tgt = b1(env, target = 'tgt2', source = 'src2')
471         assert tgt.sources[0].path == 'src2.c', \
472                 "Source has unexpected name: %s" % tgt.sources[0].path
473
474         tgt = b1(env, target = 'tgt3', source = 'src3a src3b')
475         assert tgt.sources[0].path == 'src3a.c', \
476                 "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].path
477         assert tgt.sources[1].path == 'src3b.c', \
478                 "Unexpected tgt.sources[1] name: %s" % tgt.sources[1].path
479
480         b2 = SCons.Builder.Builder(name = "b2",
481                                    src_suffix = '.2',
482                                    src_builder = b1)
483         assert b2.src_suffixes(env,{}) == ['.2', '.c'], b2.src_suffixes(env,{})
484
485         b3 = SCons.Builder.Builder(name = "b3",
486                                    action = {'.3a' : '', '.3b' : ''})
487         s = b3.src_suffixes(env,{})
488         s.sort()
489         assert s == ['.3a', '.3b'], s
490
491         b4 = SCons.Builder.Builder(name = "b4", src_suffix = '$XSUFFIX')
492         assert b4.src_suffixes(env,{}) == ['.x'], b4.src_suffixes(env,{})
493
494         b5 = SCons.Builder.Builder(name = "b5", action = {'$YSUFFIX' : ''})
495         assert b5.src_suffixes(env,{}) == ['.y'], b5.src_suffixes(env,{})
496
497     def test_suffix(self):
498         """Test Builder creation with a specified target suffix
499
500         Make sure that the '.' separator is appended to the
501         beginning if it isn't already present.
502         """
503         builder = SCons.Builder.Builder(name = "builder", suffix = '.o')
504         assert builder.get_suffix(env,{}) == '.o', builder.get_suffix(env,{})
505         builder = SCons.Builder.Builder(name = "builder", suffix = 'o')
506         assert builder.get_suffix(env,{}) == '.o', builder.get_suffix(env,{})
507         tgt = builder(env, target = 'tgt3', source = 'src3')
508         assert tgt.path == 'tgt3.o', \
509                 "Target has unexpected name: %s" % tgt[0].path
510         tgts = builder(env, target = 'tgt4a tgt4b', source = 'src4')
511         assert tgts[0].path == 'tgt4a.o', \
512                 "Target has unexpected name: %s" % tgts[0].path
513         tgts = builder(env, target = 'tgt4a tgt4b', source = 'src4')
514         assert tgts[1].path == 'tgt4b.o', \
515                 "Target has unexpected name: %s" % tgts[1].path
516
517     def test_ListBuilder(self):
518         """Testing ListBuilder class."""
519         global count
520         count = 0
521         def function2(tlist = [outfile, outfile2], **kw):
522             global count
523             count = count + 1
524             for t in kw['target']:
525                 open(str(t), 'w').write("function2\n")
526             for t in tlist:
527                 if not t in map(str, kw['target']):
528                     open(t, 'w').write("function2\n")
529             return 1
530
531         builder = SCons.Builder.Builder(action = function2, name = "function2")
532         tgts = builder(env, target = [outfile, outfile2], source = 'foo')
533         try:
534             r = tgts[0].builder.execute(target = tgts)
535         except SCons.Errors.BuildError:
536             pass
537         c = test.read(outfile, 'r')
538         assert c == "function2\n", c
539         c = test.read(outfile2, 'r')
540         assert c == "function2\n", c
541         r = tgts[1].builder.execute(target = tgts[1])
542         assert r == 1, r
543         assert count == 1, count
544
545         sub1_out = test.workpath('sub1', 'out')
546         sub2_out = test.workpath('sub2', 'out')
547
548         count = 0
549         def function3(tlist = [sub1_out, sub2_out], **kw):
550             global count
551             count = count + 1
552             for t in kw['target']:
553                 open(str(t), 'w').write("function3\n")
554             for t in tlist:
555                 if not t in map(str, kw['target']):
556                     open(t, 'w').write("function3\n")
557             return 1
558
559         builder = SCons.Builder.Builder(action = function3, name = "function3")
560         tgts = builder(env, target = [sub1_out, sub2_out], source = 'foo')
561         try:
562             r = tgts[0].builder.execute(target = tgts)
563         except:
564             pass
565         assert r == 1, r
566         c = test.read(sub1_out, 'r')
567         assert c == "function3\n", c
568         c = test.read(sub2_out, 'r')
569         assert c == "function3\n", c
570         assert os.path.exists(test.workpath('sub1'))
571         assert os.path.exists(test.workpath('sub2'))
572
573     def test_MultiStepBuilder(self):
574         """Testing MultiStepBuilder class."""
575         builder1 = SCons.Builder.Builder(name = "builder1",
576                                          action='foo',
577                                          src_suffix='.bar',
578                                          suffix='.foo')
579         builder2 = SCons.Builder.MultiStepBuilder(name = "builder2",
580                                                   action='bar',
581                                                   src_builder = builder1,
582                                                   src_suffix = '.foo')
583         tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt')
584         assert str(tgt.sources[0]) == 'test.foo', str(tgt.sources[0])
585         assert str(tgt.sources[0].sources[0]) == 'test.bar', \
586                str(tgt.sources[0].sources[0])
587         assert str(tgt.sources[1]) == 'test2.foo', str(tgt.sources[1])
588         assert str(tgt.sources[2]) == 'test3.txt', str(tgt.sources[2])
589         
590     def test_CompositeBuilder(self):
591         """Testing CompositeBuilder class."""
592         def func_action(target, source, env):
593             return 0
594         
595         builder = SCons.Builder.Builder(name = "builder",
596                                         action={ '.foo' : func_action,
597                                                  '.bar' : func_action })
598         
599         assert isinstance(builder, SCons.Builder.BuilderBase)
600         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
601         tgt = builder(env, target='test1', source='test1.foo')
602         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
603         assert isinstance(tgt.builder.action.generator, SCons.Builder.DictCmdGenerator)
604         flag = 0
605         tgt = builder(env, target='test2', source='test2.bar test1.foo')
606         try:
607             tgt.build()
608         except SCons.Errors.BuildError, e:
609             assert e.args[0] == SCons.Errors.UserError
610             flag = 1
611         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
612
613         foo_bld = SCons.Builder.Builder(name = "foo_bld",
614                                         action = 'a-foo',
615                                         src_suffix = '.ina',
616                                         suffix = '.foo')
617         assert isinstance(foo_bld, SCons.Builder.BuilderBase)
618         builder = SCons.Builder.Builder(name = "builder",
619                                         action = { '.foo' : 'foo',
620                                                    '.bar' : 'bar' },
621                                         src_builder = foo_bld)
622         assert isinstance(builder, SCons.Builder.MultiStepBuilder)
623         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
624
625         tgt = builder(env, target='t1', source='t1a.ina t1b.ina')
626         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
627
628         tgt = builder(env, target='t2', source='t2a.foo t2b.ina')
629         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder), tgt.builder.__dict__
630
631         bar_bld = SCons.Builder.Builder(name = "bar_bld",
632                                         action = 'a-bar',
633                                         src_suffix = '.inb',
634                                         suffix = '.bar')
635         assert isinstance(bar_bld, SCons.Builder.BuilderBase)
636         builder = SCons.Builder.Builder(name = "builder",
637                                         action = { '.foo' : 'foo',
638                                                    '.bar' : 'bar' },
639                                         src_builder = [foo_bld, bar_bld])
640         assert isinstance(builder, SCons.Builder.MultiStepBuilder)
641         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
642
643         tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')
644         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
645
646         tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')
647         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
648
649         flag = 0
650         tgt = builder(env, target='t5', source='test5a.foo test5b.inb')
651         try:
652             tgt.build()
653         except SCons.Errors.BuildError, e:
654             assert e.args[0] == SCons.Errors.UserError
655             flag = 1
656         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
657
658         flag = 0
659         tgt = builder(env, target='t6', source='test6a.bar test6b.ina')
660         try:
661             tgt.build()
662         except SCons.Errors.BuildError, e:
663             assert e.args[0] == SCons.Errors.UserError
664             flag = 1
665         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
666
667         flag = 0
668         tgt = builder(env, target='t4', source='test4a.ina test4b.inb')
669         try:
670             tgt.build()
671         except SCons.Errors.BuildError, e:
672             assert e.args[0] == SCons.Errors.UserError
673             flag = 1
674         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
675
676
677     def test_build_scanner(self):
678         """Testing ability to set a target scanner through a builder."""
679         global instanced
680         class TestScanner:
681             pass
682         scn = TestScanner()
683         builder = SCons.Builder.Builder(name = "builder", scanner=scn)
684         tgt = builder(env, target='foo2', source='bar')
685         assert tgt.target_scanner == scn, tgt.target_scanner
686
687         builder1 = SCons.Builder.Builder(name = "builder1",
688                                          action='foo',
689                                          src_suffix='.bar',
690                                          suffix='.foo')
691         builder2 = SCons.Builder.Builder(name = "builder2",
692                                          action='foo',
693                                          src_builder = builder1,
694                                          scanner = scn)
695         tgt = builder2(env, target='baz2', source='test.bar test2.foo test3.txt')
696         assert tgt.target_scanner == scn, tgt.target_scanner
697
698     def test_src_scanner(slf):
699         """Testing ability to set a source file scanner through a builder."""
700         global env_scanner
701         class TestScanner:
702             def key(self, env):
703                  return 'TestScannerkey'
704             def instance(self, env):
705                  return self
706         env_scanner = TestScanner()
707         builder = SCons.Builder.Builder(name = "builder", action='action')
708         tgt = builder(env, target='foo.x', source='bar')
709         src = tgt.sources[0]
710         assert tgt.target_scanner != env_scanner, tgt.target_scanner
711         assert src.source_scanner == env_scanner
712
713     def test_Builder_Args(self):
714         """Testing passing extra agrs to a builder."""
715         def buildFunc(target, source, env, foo, bar, s=self):
716             s.foo=foo
717             s.bar=bar
718
719         builder = SCons.Builder.Builder(name="builder", action=buildFunc)
720         tgt = builder(env, target='foo', source='bar', foo=1, bar=2)
721         tgt.build()
722         assert self.foo == 1, self.foo
723         assert self.bar == 2, self.bar
724
725     def test_emitter(self):
726         """Test emitter functions."""
727         def emit(target, source, env, foo=0, bar=0):
728             if foo:
729                 target.append("bar%d"%foo)
730             if bar:
731                 source.append("baz")
732             return ( target, source )
733
734         builder = SCons.Builder.Builder(name="builder", action='foo',
735                                         emitter=emit)
736         tgt = builder(env, target='foo2', source='bar')
737         assert str(tgt) == 'foo2', str(tgt)
738         assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
739
740         tgt = builder(env, target='foo3', source='bar', foo=1)
741         assert len(tgt) == 2, len(tgt)
742         assert 'foo3' in map(str, tgt), map(str, tgt)
743         assert 'bar1' in map(str, tgt), map(str, tgt)
744
745         tgt = builder(env, target='foo4', source='bar', bar=1)
746         assert str(tgt) == 'foo4', str(tgt)
747         assert len(tgt.sources) == 2, len(tgt.sources)
748         assert 'baz' in map(str, tgt.sources), map(str, tgt.sources)
749         assert 'bar' in map(str, tgt.sources), map(str, tgt.sources)
750
751         env2=Environment(FOO=emit)
752         builder2=SCons.Builder.Builder(name="builder2", action='foo',
753                                        emitter="$FOO")
754
755         tgt = builder2(env2, target='foo5', source='bar')
756         assert str(tgt) == 'foo5', str(tgt)
757         assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
758
759         tgt = builder2(env2, target='foo6', source='bar', foo=2)
760         assert len(tgt) == 2, len(tgt)
761         assert 'foo6' in map(str, tgt), map(str, tgt)
762         assert 'bar2' in map(str, tgt), map(str, tgt)
763
764         tgt = builder2(env2, target='foo7', source='bar', bar=1)
765         assert str(tgt) == 'foo7', str(tgt)
766         assert len(tgt.sources) == 2, len(tgt.sources)
767         assert 'baz' in map(str, tgt.sources), map(str, tgt.sources)
768         assert 'bar' in map(str, tgt.sources), map(str, tgt.sources)
769
770 if __name__ == "__main__":
771     suite = unittest.makeSuite(BuilderTestCase, 'test_')
772     if not unittest.TextTestRunner().run(suite).wasSuccessful():
773         sys.exit(1)