2 # Copyright (c) 2001, 2002 Steven Knight
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:
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
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.
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
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.
41 # Initial setup of the common environment for all tests,
42 # a temporary working directory containing a
43 # script for writing arguments to an output file.
45 # We don't do this as a setUp() method because it's
46 # unnecessary to create a separate directory and script
47 # for each test, they can just use the one.
48 test = TestCmd.TestCmd(workdir = '')
50 test.write('act.py', """import os, string, sys
51 f = open(sys.argv[1], 'w')
52 f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n")
55 f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n")
62 act_py = test.workpath('act.py')
63 outfile = test.workpath('outfile')
64 outfile2 = test.workpath('outfile')
74 def get_scanner(self, ext):
78 class BuilderTestCase(unittest.TestCase):
80 def test__call__(self):
81 """Test calling a builder to establish source dependencies
84 def __init__(self, name):
90 def builder_set(self, builder):
91 self.builder = builder
92 def env_set(self, env, safe=0):
94 def add_source(self, source):
95 self.sources.extend(source)
96 builder = SCons.Builder.Builder(name="builder", action="foo", node_factory=Node)
100 builder(env, target = n1, source = n2)
102 assert n1.builder == builder
103 assert n1.sources == [n2]
106 target = builder(env, target = 'n3', source = 'n4')
107 assert target.name == 'n3'
108 assert target.sources[0].name == 'n4'
110 targets = builder(env, target = 'n4 n5', source = ['n6 n7'])
111 assert targets[0].name == 'n4'
112 assert targets[0].sources[0].name == 'n6 n7'
113 assert targets[1].name == 'n5'
114 assert targets[1].sources[0].name == 'n6 n7'
116 target = builder(env, target = ['n8 n9'], source = 'n10 n11')
117 assert target.name == 'n8 n9'
118 assert target.sources[0].name == 'n10'
119 assert target.sources[1].name == 'n11'
121 def test_noname(self):
122 """Test error reporting for missing name
124 Verify that the Builder constructor gives an error message if the
128 b = SCons.Builder.Builder()
129 except SCons.Errors.UserError:
134 def test_action(self):
135 """Test Builder creation
137 Verify that we can retrieve the supplied action attribute.
139 builder = SCons.Builder.Builder(name="builder", action="foo")
140 assert builder.action.command == "foo"
143 """Test simple comparisons of Builder objects
145 b1 = SCons.Builder.Builder(name="b1", src_suffix = '.o')
146 b2 = SCons.Builder.Builder(name="b1", src_suffix = '.o')
148 b3 = SCons.Builder.Builder(name="b3", src_suffix = '.x')
152 def test_execute(self):
153 """Test execution of simple Builder objects
155 One Builder is a string that executes an external command,
156 one is an internal Python function, one is a list
157 containing one of each.
161 builder = apply(SCons.Builder.Builder, (), kw)
164 builder.action.show = no_show
167 python = sys.executable
169 cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile)
171 builder = MyBuilder(action = cmd1, name = "cmd1")
172 r = builder.execute()
174 c = test.read(outfile, 'r')
175 assert c == "act.py: 'xyzzy'\n", c
177 cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
179 builder = MyBuilder(action = cmd2, name = "cmd2")
180 r = builder.execute(target = 'foo')
182 c = test.read(outfile, 'r')
183 assert c == "act.py: 'foo'\n", c
185 cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
187 builder = MyBuilder(action = cmd3, name = "cmd3")
188 r = builder.execute(target = ['aaa', 'bbb'])
190 c = test.read(outfile, 'r')
191 assert c == "act.py: 'aaa' 'bbb'\n", c
193 cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
195 builder = MyBuilder(action = cmd4, name = "cmd4")
196 r = builder.execute(source = ['one', 'two'])
198 c = test.read(outfile, 'r')
199 assert c == "act.py: 'one' 'two'\n", c
201 cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile)
203 builder = MyBuilder(action = cmd4, name = "cmd4")
204 r = builder.execute(source = ['three', 'four', 'five'])
206 c = test.read(outfile, 'r')
207 assert c == "act.py: 'three' 'four'\n", c
209 cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile)
211 builder = MyBuilder(action = cmd5, name = "cmd5")
212 r = builder.execute(target = 'out5', env = {'ENV' : {'XYZZY' : 'xyzzy'}})
214 c = test.read(outfile, 'r')
215 assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c
218 def __init__(self, str):
223 cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile)
225 builder = MyBuilder(action = cmd6, name = "cmd6")
226 r = builder.execute(target = [Obj('111'), Obj('222')],
227 source = [Obj('333'), Obj('444'), Obj('555')])
229 c = test.read(outfile, 'r')
230 assert c == "act.py: '222' '111' '333' '444'\n", c
232 cmd7 = '%s %s %s one\n\n%s %s %s two' % (python, act_py, outfile,
233 python, act_py, outfile)
234 expect7 = '%s %s %s one\n%s %s %s two\n' % (python, act_py, outfile,
235 python, act_py, outfile)
237 builder = MyBuilder(action = cmd7, name = "cmd7")
243 show_string = show_string + string + "\n"
244 builder.action.show = my_show
246 r = builder.execute()
248 assert show_string == expect7, show_string
255 if not type(kw['target']) is type([]):
256 kw['target'] = [ kw['target'] ]
257 for t in kw['target']:
258 open(t, 'w').write("function1\n")
261 builder = MyBuilder(action = function1, name = "function1")
262 r = builder.execute(target = [outfile, outfile2])
265 c = test.read(outfile, 'r')
266 assert c == "function1\n", c
267 c = test.read(outfile2, 'r')
268 assert c == "function1\n", c
271 def __init__(self, **kw):
272 open(kw['out'], 'w').write("class1a\n")
274 builder = MyBuilder(action = class1a, name = "class1a")
275 r = builder.execute(out = outfile)
276 assert r.__class__ == class1a
277 c = test.read(outfile, 'r')
278 assert c == "class1a\n", c
281 def __call__(self, **kw):
282 open(kw['out'], 'w').write("class1b\n")
285 builder = MyBuilder(action = class1b(), name = "class1b")
286 r = builder.execute(out = outfile)
288 c = test.read(outfile, 'r')
289 assert c == "class1b\n", c
291 cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile)
294 open(kw['out'], 'a').write("function2\n")
298 def __call__(self, **kw):
299 open(kw['out'], 'a').write("class2a\n")
303 def __init__(self, **kw):
304 open(kw['out'], 'a').write("class2b\n")
306 builder = MyBuilder(action = [cmd2, function2, class2a(), class2b], name = "clist")
307 r = builder.execute(out = outfile)
308 assert r.__class__ == class2b
309 c = test.read(outfile, 'r')
310 assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
313 # NT treats execs of directories and non-executable files
314 # as "file not found" errors
315 expect_nonexistent = 1
316 expect_nonexecutable = 1
318 expect_nonexistent = 127
319 expect_nonexecutable = 126
321 # Test that a nonexistent command returns 127
322 builder = MyBuilder(action = python + "_XyZzY_", name="badcmd")
323 r = builder.execute(out = outfile)
324 assert r == expect_nonexistent, "r == %d" % r
326 # Test that trying to execute a directory returns 126
327 dir, tail = os.path.split(python)
328 builder = MyBuilder(action = dir, name = "dir")
329 r = builder.execute(out = outfile)
330 assert r == expect_nonexecutable, "r == %d" % r
332 # Test that trying to execute a non-executable file returns 126
333 builder = MyBuilder(action = outfile, name = "badfile")
334 r = builder.execute(out = outfile)
335 assert r == expect_nonexecutable, "r == %d" % r
337 def test_get_contents(self):
338 """Test returning the signature contents of a Builder
341 b1 = SCons.Builder.Builder(name = "b1", action = "foo")
342 contents = b1.get_contents()
343 assert contents == "foo", contents
345 b2 = SCons.Builder.Builder(name = "b2", action = Func)
346 contents = b2.get_contents()
347 assert contents == "\177\036\000\177\037\000d\000\000S", repr(contents)
349 b3 = SCons.Builder.Builder(name = "b3", action = ["foo", Func, "bar"])
350 contents = b3.get_contents()
351 assert contents == "foo\177\036\000\177\037\000d\000\000Sbar", repr(contents)
353 b4 = SCons.Builder.Builder(name = "b4", action = "$_LIBFLAGS $_LIBDIRFLAGS $_INCFLAGS")
354 kw = {'LIBS' : ['l1', 'l2'],
355 'LIBLINKPREFIX' : '-l',
356 'LIBLINKSUFFIX' : '',
358 'LIBDIRPREFIX' : '-L',
359 'LIBDIRSUFFIX' : 'X',
360 'CPPPATH' : ['c', 'p'],
364 contents = apply(b4.get_raw_contents, (), kw)
365 assert contents == "-ll1 -ll2 $( -LlibX $) $( -Ic -Ip $)", contents
367 contents = apply(b4.get_contents, (), kw)
368 assert contents == "-ll1 -ll2", "'%s'" % contents
370 # SCons.Node.FS has been imported by our import of
371 # SCons.Node.Builder. It's kind of bogus that we don't
372 # import this ourselves before using it this way, but it's
373 # maybe a little cleaner than tying these tests directly
374 # to the other module via a direct import.
375 kw['dir'] = SCons.Node.FS.default_fs.Dir('d')
377 contents = apply(b4.get_raw_contents, (), kw)
378 expect = os.path.normpath("-ll1 -ll2 $( -Ld/libX $) $( -Id/c -Id/p $)")
379 assert contents == expect, contents + " != " + expect
381 contents = apply(b4.get_contents, (), kw)
382 expect = os.path.normpath("-ll1 -ll2")
383 assert contents == expect, contents + " != " + expect
385 def test_node_factory(self):
386 """Test a Builder that creates nodes of a specified class
390 def FooFactory(target):
393 builder = SCons.Builder.Builder(name = "builder", node_factory = FooFactory)
394 assert builder.node_factory is FooFactory
396 def test_prefix(self):
397 """Test Builder creation with a specified target prefix
399 Make sure that there is no '.' separator appended.
401 builder = SCons.Builder.Builder(name = "builder", prefix = 'lib.')
402 assert builder.prefix == 'lib.'
403 builder = SCons.Builder.Builder(name = "builder", prefix = 'lib')
404 assert builder.prefix == 'lib'
405 tgt = builder(env, target = 'tgt1', source = 'src1')
406 assert tgt.path == 'libtgt1', \
407 "Target has unexpected name: %s" % tgt.path
408 tgts = builder(env, target = 'tgt2a tgt2b', source = 'src2')
409 assert tgts[0].path == 'libtgt2a', \
410 "Target has unexpected name: %s" % tgts[0].path
411 assert tgts[1].path == 'libtgt2b', \
412 "Target has unexpected name: %s" % tgts[1].path
414 def test_src_suffix(self):
415 """Test Builder creation with a specified source file suffix
417 Make sure that the '.' separator is appended to the
418 beginning if it isn't already present.
420 builder = SCons.Builder.Builder(name = "builder", src_suffix = '.c')
421 assert builder.src_suffixes() == ['.c'], builder.src_suffixes()
423 tgt = builder(env, target = 'tgt2', source = 'src2')
424 assert tgt.sources[0].path == 'src2.c', \
425 "Source has unexpected name: %s" % tgt.sources[0].path
427 tgt = builder(env, target = 'tgt3', source = 'src3a src3b')
428 assert tgt.sources[0].path == 'src3a.c', \
429 "Sources[0] has unexpected name: %s" % tgt.sources[0].path
430 assert tgt.sources[1].path == 'src3b.c', \
431 "Sources[1] has unexpected name: %s" % tgt.sources[1].path
433 b2 = SCons.Builder.Builder(name = "b2", src_suffix = '.2', src_builder = builder)
434 assert b2.src_suffixes() == ['.2', '.c'], b2.src_suffixes()
436 b3 = SCons.Builder.Builder(name = "b2", action = {'.3a' : '', '.3b' : ''})
437 s = b3.src_suffixes()
439 assert s == ['.3a', '.3b'], s
441 def test_suffix(self):
442 """Test Builder creation with a specified target suffix
444 Make sure that the '.' separator is appended to the
445 beginning if it isn't already present.
447 builder = SCons.Builder.Builder(name = "builder", suffix = '.o')
448 assert builder.suffix == '.o'
449 builder = SCons.Builder.Builder(name = "builder", suffix = 'o')
450 assert builder.suffix == '.o'
451 tgt = builder(env, target = 'tgt3', source = 'src3')
452 assert tgt.path == 'tgt3.o', \
453 "Target has unexpected name: %s" % tgt[0].path
454 tgts = builder(env, target = 'tgt4a tgt4b', source = 'src4')
455 assert tgts[0].path == 'tgt4a.o', \
456 "Target has unexpected name: %s" % tgts[0].path
457 tgts = builder(env, target = 'tgt4a tgt4b', source = 'src4')
458 assert tgts[1].path == 'tgt4b.o', \
459 "Target has unexpected name: %s" % tgts[1].path
461 def test_ListBuilder(self):
462 """Testing ListBuilder class."""
468 if not type(kw['target']) is type([]):
469 kw['target'] = [ kw['target'] ]
470 for t in kw['target']:
471 open(t, 'w').write("function2\n")
474 builder = SCons.Builder.Builder(action = function2, name = "function2")
475 tgts = builder(env, target = [outfile, outfile2], source = 'foo')
476 r = tgts[0].builder.execute(target = tgts[0])
478 c = test.read(outfile, 'r')
479 assert c == "function2\n", c
480 c = test.read(outfile2, 'r')
481 assert c == "function2\n", c
482 r = tgts[1].builder.execute(target = tgts[1])
484 assert count == 1, count
486 def test_MultiStepBuilder(self):
487 """Testing MultiStepBuilder class."""
488 builder1 = SCons.Builder.Builder(name = "builder1",
492 builder2 = SCons.Builder.MultiStepBuilder(name = "builder2",
494 src_builder = builder1,
496 tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt')
497 assert str(tgt.sources[0]) == 'test.foo', str(tgt.sources[0])
498 assert str(tgt.sources[0].sources[0]) == 'test.bar', \
499 str(tgt.sources[0].sources[0])
500 assert str(tgt.sources[1]) == 'test2.foo', str(tgt.sources[1])
501 assert str(tgt.sources[2]) == 'test3.txt', str(tgt.sources[2])
503 def test_CompositeBuilder(self):
504 """Testing CompositeBuilder class."""
505 builder = SCons.Builder.Builder(name = "builder",
506 action={ '.foo' : 'foo',
509 assert isinstance(builder, SCons.Builder.CompositeBuilder)
510 tgt = builder(env, target='test1', source='test1.foo')
511 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
512 assert tgt.builder.action.command == 'foo'
513 tgt = builder(env, target='test2', source='test2.bar')
514 assert tgt.builder.action.command == 'bar'
517 tgt = builder(env, target='test2', source='test2.bar test1.foo')
518 except SCons.Errors.UserError:
520 assert flag, "UserError should be thrown when we build targets with files of different suffixes."
522 foo_bld = SCons.Builder.Builder(name = "foo_bld",
526 assert isinstance(foo_bld, SCons.Builder.BuilderBase)
527 builder = SCons.Builder.Builder(name = "builder",
528 action = { '.foo' : 'foo',
530 src_builder = foo_bld)
531 assert isinstance(builder, SCons.Builder.CompositeBuilder)
533 tgt = builder(env, target='t1', source='t1a.ina t1b.ina')
534 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
536 tgt = builder(env, target='t2', source='t2a.foo t2b.ina')
537 assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder), tgt.builder.__dict__
539 bar_bld = SCons.Builder.Builder(name = "bar_bld",
543 assert isinstance(bar_bld, SCons.Builder.BuilderBase)
544 builder = SCons.Builder.Builder(name = "builder",
545 action = { '.foo' : 'foo',
547 src_builder = [foo_bld, bar_bld])
548 assert isinstance(builder, SCons.Builder.CompositeBuilder)
550 tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')
551 assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
553 tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')
554 assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
558 tgt = builder(env, target='t5', source='test5a.foo test5b.inb')
559 except SCons.Errors.UserError:
561 assert flag, "UserError should be thrown when we build targets with files of different suffixes."
565 tgt = builder(env, target='t6', source='test6a.bar test6b.ina')
566 except SCons.Errors.UserError:
568 assert flag, "UserError should be thrown when we build targets with files of different suffixes."
572 tgt = builder(env, target='t4', source='test4a.ina test4b.inb')
573 except SCons.Errors.UserError:
575 assert flag, "UserError should be thrown when we build targets with files of different suffixes."
577 def test_build_scanner(self):
578 """Testing ability to set a target scanner through a builder."""
581 def instance(self, env):
586 builder=SCons.Builder.Builder(name = "builder", scanner=scn)
587 tgt = builder(env, target='foo', source='bar')
588 assert scn in tgt.scanners, tgt.scanners
592 builder1 = SCons.Builder.Builder(name = "builder1",
596 builder2 = SCons.Builder.Builder(name = "builder2",
598 src_builder = builder1,
600 tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt')
601 assert scn in tgt.scanners, tgt.scanners
604 def test_src_scanner(slf):
605 """Testing ability to set a source file scanner through a builder."""
609 return 'TestScannerkey'
610 def instance(self, env):
612 env_scanner = TestScanner()
613 builder = SCons.Builder.Builder(name = "builder", action='action')
614 tgt = builder(env, target='foo', source='bar')
615 assert not tgt.scanners == [ env_scanner ]
616 assert tgt.sources[0].scanners == [ env_scanner ]
618 if __name__ == "__main__":
619 suite = unittest.makeSuite(BuilderTestCase, 'test_')
620 if not unittest.TextTestRunner().run(suite).wasSuccessful():