Improve new post-PathList refactoring performance. (Charles Crain)
[scons.git] / src / engine / SCons / ActionTests.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 # 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
34 import re
35 import StringIO
36 import sys
37 import types
38 import unittest
39 import UserDict
40
41 import SCons.Action
42 import SCons.Environment
43 import SCons.Errors
44
45 import TestCmd
46
47 # Initial setup of the common environment for all tests,
48 # a temporary working directory containing a
49 # script for writing arguments to an output file.
50 #
51 # We don't do this as a setUp() method because it's
52 # unnecessary to create a separate directory and script
53 # for each test, they can just use the one.
54 test = TestCmd.TestCmd(workdir = '')
55
56 test.write('act.py', """import os, string, sys
57 f = open(sys.argv[1], 'w')
58 f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n")
59 try:
60     if sys.argv[3]:
61         f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n")
62 except:
63     pass
64 f.close()
65 if os.environ.has_key( 'ACTPY_PIPE' ):
66     sys.stdout.write( 'act.py: stdout: executed act.py\\n' )
67     sys.stderr.write( 'act.py: stderr: executed act.py\\n' ) 
68 sys.exit(0)
69 """)
70
71 act_py = test.workpath('act.py')
72
73 outfile = test.workpath('outfile')
74 outfile2 = test.workpath('outfile2')
75
76 scons_env = SCons.Environment.Environment()
77
78 # Capture all the stuff the Actions will print,
79 # so it doesn't clutter the output.
80 sys.stdout = StringIO.StringIO()
81
82 class Environment:
83     def __init__(self, **kw):
84         self.d = {}
85         self.d['SHELL'] = scons_env['SHELL']
86         self.d['SPAWN'] = scons_env['SPAWN']
87         self.d['PSPAWN'] = scons_env['PSPAWN']
88         self.d['ESCAPE'] = scons_env['ESCAPE']
89         for k, v in kw.items():
90             self.d[k] = v
91     def subst(self, s):
92         if not SCons.Util.is_String(s):
93             return s
94         try:
95             if s[0] == '$':
96                 if s[1] == '{':
97                     return self.d.get(s[2:-1], '')
98                 else:
99                     return self.d.get(s[1:], '')
100         except IndexError:
101             pass
102         return self.d.get(s, s)
103     def __getitem__(self, item):
104         return self.d[item]
105     def __setitem__(self, item, value):
106         self.d[item] = value
107     def has_key(self, item):
108         return self.d.has_key(item)
109     def get(self, key, value):
110         return self.d.get(key, value)
111     def items(self):
112         return self.d.items()
113     def Dictionary(self):
114         return self.d
115     def Copy(self, **kw):
116         res = Environment()
117         res.d = SCons.Environment.our_deepcopy(self.d)
118         for k, v in kw.items():
119             res.d[k] = v        
120         return res
121     def sig_dict(self):
122         d = {}
123         for k,v in self.items(): d[k] = v
124         d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
125         d['TARGET'] = d['TARGETS'][0]
126         d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
127         d['SOURCE'] = d['SOURCES'][0]
128         return d
129
130 class DummyNode:
131     def __init__(self, name):
132         self.name = name
133     def __str__(self):
134         return self.name
135     def rfile(self):
136         return self
137     def get_subst_proxy(self):
138         return self
139
140 if os.name == 'java':
141     python = os.path.join(sys.prefix, 'jython')
142 else:
143     python = sys.executable
144
145 class ActionTestCase(unittest.TestCase):
146
147     def test_factory(self):
148         """Test the Action factory
149         """
150         def foo():
151             pass
152         def bar():
153             pass
154         a1 = SCons.Action.Action(foo)
155         assert isinstance(a1, SCons.Action.FunctionAction), a1
156         assert a1.execfunction == foo, a1.execfunction
157
158         a2 = SCons.Action.Action("string")
159         assert isinstance(a2, SCons.Action.CommandAction), a2
160         assert a2.cmd_list == "string", a2.cmd_list
161
162         if hasattr(types, 'UnicodeType'):
163             exec "a3 = SCons.Action.Action(u'string')"
164             exec "assert isinstance(a3, SCons.Action.CommandAction), a3"
165
166         a4 = SCons.Action.Action(["x", "y", "z", [ "a", "b", "c"]])
167         assert isinstance(a4, SCons.Action.ListAction), a4
168         assert isinstance(a4.list[0], SCons.Action.CommandAction), a4.list[0]
169         assert a4.list[0].cmd_list == "x", a4.list[0].cmd_list
170         assert isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1]
171         assert a4.list[1].cmd_list == "y", a4.list[1].cmd_list
172         assert isinstance(a4.list[2], SCons.Action.CommandAction), a4.list[2]
173         assert a4.list[2].cmd_list == "z", a4.list[2].cmd_list
174         assert isinstance(a4.list[3], SCons.Action.CommandAction), a4.list[3]
175         assert a4.list[3].cmd_list == [ "a", "b", "c" ], a4.list[3].cmd_list
176
177         a5 = SCons.Action.Action(1)
178         assert a5 is None, a5
179
180         a6 = SCons.Action.Action(a1)
181         assert a6 is a1, a6
182
183         a7 = SCons.Action.Action([[ "explicit", "command", "line" ]])
184         assert isinstance(a7, SCons.Action.CommandAction), a7
185         assert a7.cmd_list == [ "explicit", "command", "line" ], a7.cmd_list
186
187         a8 = SCons.Action.Action(["a8"])
188         assert isinstance(a8, SCons.Action.CommandAction), a8
189         assert a8.cmd_list == "a8", a8.cmd_list
190
191         a9 = SCons.Action.Action("x\ny\nz")
192         assert isinstance(a9, SCons.Action.ListAction), a9
193         assert isinstance(a9.list[0], SCons.Action.CommandAction), a9.list[0]
194         assert a9.list[0].cmd_list == "x", a9.list[0].cmd_list
195         assert isinstance(a9.list[1], SCons.Action.CommandAction), a9.list[1]
196         assert a9.list[1].cmd_list == "y", a9.list[1].cmd_list
197         assert isinstance(a9.list[2], SCons.Action.CommandAction), a9.list[2]
198         assert a9.list[2].cmd_list == "z", a9.list[2].cmd_list
199
200         a10 = SCons.Action.Action(["x", foo, "z"])
201         assert isinstance(a10, SCons.Action.ListAction), a10
202         assert isinstance(a10.list[0], SCons.Action.CommandAction), a10.list[0]
203         assert a10.list[0].cmd_list == "x", a10.list[0].cmd_list
204         assert isinstance(a10.list[1], SCons.Action.FunctionAction), a10.list[1]
205         assert a10.list[1].execfunction == foo, a10.list[1].execfunction
206         assert isinstance(a10.list[2], SCons.Action.CommandAction), a10.list[2]
207         assert a10.list[2].cmd_list == "z", a10.list[2].cmd_list
208
209         a11 = SCons.Action.Action(foo, strfunction=bar)
210         assert isinstance(a11, SCons.Action.FunctionAction), a11
211         assert a11.execfunction == foo, a11.execfunction
212         assert a11.strfunction == bar, a11.strfunction
213
214 class ActionBaseTestCase(unittest.TestCase):
215
216     def test_cmp(self):
217         """Test Action comparison
218         """
219         a1 = SCons.Action.Action("x")
220         a2 = SCons.Action.Action("x")
221         assert a1 == a2
222         a3 = SCons.Action.Action("y")
223         assert a1 != a3
224         assert a2 != a3
225
226     def test_show(self):
227         """Test the show() method
228         """
229         save_stdout = sys.stdout
230
231         save = SCons.Action.print_actions
232         SCons.Action.print_actions = 0
233
234         sio = StringIO.StringIO()
235         sys.stdout = sio
236         a = SCons.Action.Action("x")
237         a.show("xyzzy")
238         s = sio.getvalue()
239         assert s == "", s
240
241         SCons.Action.print_actions = 1
242
243         sio = StringIO.StringIO()
244         sys.stdout = sio
245         a.show("foobar")
246         s = sio.getvalue()
247         assert s == "foobar\n", s
248
249         SCons.Action.print_actions = save
250         sys.stdout = save_stdout
251
252     def test_get_actions(self):
253         """Test the get_actions() method
254         """
255         a = SCons.Action.Action("x")
256         l = a.get_actions()
257         assert l == [a], l
258
259     def test_add(self):
260         """Test adding Actions to stuff."""
261         # Adding actions to other Actions or to stuff that can
262         # be converted into an Action should produce a ListAction
263         # containing all the Actions.
264         def bar():
265             return None
266         baz = SCons.Action.CommandGenerator(bar)
267         act1 = SCons.Action.Action('foo bar')
268         act2 = SCons.Action.Action([ 'foo', bar ])
269
270         sum = act1 + act2
271         assert isinstance(sum, SCons.Action.ListAction), str(sum)
272         assert len(sum.list) == 3, len(sum.list)
273         assert map(lambda x: isinstance(x, SCons.Action.ActionBase),
274                    sum.list) == [ 1, 1, 1 ]
275
276         sum = act1 + act1
277         assert isinstance(sum, SCons.Action.ListAction), str(sum)
278         assert len(sum.list) == 2, len(sum.list)
279
280         sum = act2 + act2
281         assert isinstance(sum, SCons.Action.ListAction), str(sum)
282         assert len(sum.list) == 4, len(sum.list)
283
284         # Should also be able to add command generators to each other
285         # or to actions
286         sum = baz + baz
287         assert isinstance(sum, SCons.Action.ListAction), str(sum)
288         assert len(sum.list) == 2, len(sum.list)
289
290         sum = baz + act1
291         assert isinstance(sum, SCons.Action.ListAction), str(sum)
292         assert len(sum.list) == 2, len(sum.list)
293
294         sum = act2 + baz
295         assert isinstance(sum, SCons.Action.ListAction), str(sum)
296         assert len(sum.list) == 3, len(sum.list)
297
298         # Also should be able to add Actions to anything that can
299         # be converted into an action.
300         sum = act1 + bar
301         assert isinstance(sum, SCons.Action.ListAction), str(sum)
302         assert len(sum.list) == 2, len(sum.list)
303         assert isinstance(sum.list[1], SCons.Action.FunctionAction)
304
305         sum = 'foo bar' + act2
306         assert isinstance(sum, SCons.Action.ListAction), str(sum)
307         assert len(sum.list) == 3, len(sum.list)
308         assert isinstance(sum.list[0], SCons.Action.CommandAction)
309
310         sum = [ 'foo', 'bar' ] + act1
311         assert isinstance(sum, SCons.Action.ListAction), str(sum)
312         assert len(sum.list) == 3, sum.list
313         assert isinstance(sum.list[0], SCons.Action.CommandAction)
314         assert isinstance(sum.list[1], SCons.Action.CommandAction)
315
316         sum = act2 + [ baz, bar ]
317         assert isinstance(sum, SCons.Action.ListAction), str(sum)
318         assert len(sum.list) == 4, len(sum.list)
319         assert isinstance(sum.list[2], SCons.Action.CommandGeneratorAction)
320         assert isinstance(sum.list[3], SCons.Action.FunctionAction)
321
322         try:
323             sum = act2 + 1
324         except TypeError:
325             pass
326         else:
327             assert 0, "Should have thrown a TypeError adding to an int."
328
329         try:
330             sum = 1 + act2
331         except TypeError:
332             pass
333         else:
334             assert 0, "Should have thrown a TypeError adding to an int."
335         
336 class CommandActionTestCase(unittest.TestCase):
337
338     def test_init(self):
339         """Test creation of a command Action
340         """
341         a = SCons.Action.CommandAction(["xyzzy"])
342         assert a.cmd_list == [ "xyzzy" ], a.cmd_list
343
344     def test_strfunction(self):
345         """Test fetching the string representation of command Actions
346         """
347             
348         act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
349         s = act.strfunction([], [], Environment())
350         assert s == ['xyzzy'], s
351         s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
352         assert s == ['xyzzy target source'], s
353         s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
354                             [DummyNode('s1'), DummyNode('s2')], Environment())
355         assert s == ['xyzzy t1 s1'], s
356
357         act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
358         s = act.strfunction([], [], Environment())
359         assert s == ['xyzzy'], s
360         s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
361         assert s == ['xyzzy target source'], s
362         s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
363                             [DummyNode('s1'), DummyNode('s2')], Environment())
364         assert s == ['xyzzy t1 t2 s1 s2'], s
365
366         act = SCons.Action.CommandAction(['xyzzy',
367                                           '$TARGET', '$SOURCE',
368                                           '$TARGETS', '$SOURCES'])
369         s = act.strfunction([], [], Environment())
370         assert s == ['xyzzy'], s
371         s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
372         assert s == ['xyzzy target source target source'], s
373         s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
374                             [DummyNode('s1'), DummyNode('s2')], Environment())
375         assert s == ['xyzzy t1 s1 t1 t2 s1 s2'], s
376
377     def test_execute(self):
378         """Test execution of command Actions
379
380         """
381         try:
382             env = self.env
383         except AttributeError:
384             env = Environment()
385             
386         cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile)
387
388         act = SCons.Action.CommandAction(cmd1)
389         r = act([], [], env.Copy())
390         assert r == 0
391         c = test.read(outfile, 'r')
392         assert c == "act.py: 'xyzzy'\n", c
393
394         cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
395
396         act = SCons.Action.CommandAction(cmd2)
397         r = act(DummyNode('foo'), [], env.Copy())
398         assert r == 0
399         c = test.read(outfile, 'r')
400         assert c == "act.py: 'foo'\n", c
401
402         cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
403
404         act = SCons.Action.CommandAction(cmd3)
405         r = act(map(DummyNode, ['aaa', 'bbb']), [], env.Copy())
406         assert r == 0
407         c = test.read(outfile, 'r')
408         assert c == "act.py: 'aaa' 'bbb'\n", c
409
410         cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
411
412         act = SCons.Action.CommandAction(cmd4)
413         r = act([], [DummyNode('one'), DummyNode('two')], env.Copy())
414         assert r == 0
415         c = test.read(outfile, 'r')
416         assert c == "act.py: 'one' 'two'\n", c
417
418         cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile)
419
420         act = SCons.Action.CommandAction(cmd4)
421         r = act([],
422                 source = [DummyNode('three'),
423                           DummyNode('four'),
424                           DummyNode('five')],
425                 env = env.Copy())
426         assert r == 0
427         c = test.read(outfile, 'r')
428         assert c == "act.py: 'three' 'four'\n", c
429
430         cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile)
431         
432         act = SCons.Action.CommandAction(cmd5)
433         env5 = Environment()
434         if scons_env.has_key('ENV'):
435             env5['ENV'] = scons_env['ENV']
436             PATH = scons_env['ENV'].get('PATH', '')
437         else:
438             env5['ENV'] = {}
439             PATH = ''
440         
441         env5['ENV']['XYZZY'] = 'xyzzy'
442         r = act(target = DummyNode('out5'), source = [], env = env5)
443
444         act = SCons.Action.CommandAction(cmd5)
445         r = act(target = DummyNode('out5'),
446                 source = [],
447                 env = env.Copy(ENV = {'XYZZY' : 'xyzzy',
448                                       'PATH' : PATH}))
449         assert r == 0
450         c = test.read(outfile, 'r')
451         assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c
452
453         class Obj:
454             def __init__(self, str):
455                 self._str = str
456             def __str__(self):
457                 return self._str
458             def rfile(self):
459                 return self
460             def get_subst_proxy(self):
461                 return self
462
463         cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile)
464
465         act = SCons.Action.CommandAction(cmd6)
466         r = act(target = [Obj('111'), Obj('222')],
467                         source = [Obj('333'), Obj('444'), Obj('555')],
468                         env = env.Copy())
469         assert r == 0
470         c = test.read(outfile, 'r')
471         assert c == "act.py: '222' '111' '333' '444'\n", c
472
473         cmd7 = '%s %s %s one\n\n%s %s %s two' % (python, act_py, outfile,
474                                                  python, act_py, outfile)
475         expect7 = '%s %s %s one\n%s %s %s two\n' % (python, act_py, outfile,
476                                                     python, act_py, outfile)
477
478         act = SCons.Action.CommandAction(cmd7)
479
480         global show_string 
481         show_string = ""
482         def my_show(string):
483             global show_string
484             show_string = show_string + string + "\n"
485         act.show = my_show
486
487         r = act([], [], env.Copy())
488         assert r == 0
489         assert show_string == expect7, show_string
490
491         if os.name == 'nt':
492             # NT treats execs of directories and non-executable files
493             # as "file not found" errors
494             expect_nonexistent = 1
495             expect_nonexecutable = 1
496         elif sys.platform == 'cygwin':
497             expect_nonexistent = 127
498             expect_nonexecutable = 127
499         else:
500             expect_nonexistent = 127
501             expect_nonexecutable = 126
502
503         # Test that a nonexistent command returns 127
504         act = SCons.Action.CommandAction(python + "_XyZzY_")
505         r = act([], [], env.Copy(out = outfile))
506         assert r == expect_nonexistent, "r == %d" % r
507
508         # Test that trying to execute a directory returns 126
509         dir, tail = os.path.split(python)
510         act = SCons.Action.CommandAction(dir)
511         r = act([], [], env.Copy(out = outfile))
512         assert r == expect_nonexecutable, "r == %d" % r
513
514         # Test that trying to execute a non-executable file returns 126
515         act = SCons.Action.CommandAction(outfile)
516         r = act([], [], env.Copy(out = outfile))
517         assert r == expect_nonexecutable, "r == %d" % r
518
519
520     def test_pipe_execute(self):
521         """Test capturing piped output from an action
522         """
523         pipe_file = open( test.workpath('pipe.out'), "w" )
524         self.env = Environment(ENV = {'ACTPY_PIPE' : '1'}, PIPE_BUILD = 1,
525                                PSTDOUT = pipe_file, PSTDERR = pipe_file)
526         # everything should also work when piping output
527         self.test_execute()
528         self.env['PSTDOUT'].close()
529         pipe_out = test.read( test.workpath('pipe.out') )
530         if sys.platform == 'win32':
531             cr = '\r'
532         else:
533             cr = ''
534         found = re.findall( "act.py: stdout: executed act.py%s\nact.py: stderr: executed act.py%s\n" % (cr, cr), pipe_out )
535         assert len(found) == 8, found
536
537     def test_set_handler(self):
538         """Test setting the command handler...
539         """
540         class Test:
541             def __init__(self):
542                 self.executed = 0
543         t=Test()
544         def func(sh, escape, cmd, args, env, test=t):
545             test.executed = args
546             test.shell = sh
547             return 0
548         def escape_func(cmd):
549             return '**' + cmd + '**'
550
551         class LiteralStr:
552             def __init__(self, x):
553                 self.data = x
554             def __str__(self):
555                 return self.data
556             def is_literal(self):
557                 return 1
558
559         try:
560             SCons.Action.SetCommandHandler(func)
561         except SCons.Errors.UserError:
562             pass
563         else:
564             assert 0, "should have gotten user error"
565             
566         a = SCons.Action.CommandAction(["xyzzy"])
567         a([], [], Environment(SPAWN = func))
568         assert t.executed == [ 'xyzzy' ]
569
570         a = SCons.Action.CommandAction(["xyzzy"])
571         a([], [], Environment(SPAWN = func, SHELL = 'fake shell'))
572         assert t.executed == [ 'xyzzy' ]
573         assert t.shell == 'fake shell'
574
575         a = SCons.Action.CommandAction([ LiteralStr("xyzzy") ])
576         a([], [], Environment(SPAWN = func, ESCAPE = escape_func))
577         assert t.executed == [ '**xyzzy**' ], t.executed
578
579     def test_get_raw_contents(self):
580         """Test fetching the contents of a command Action
581         """
582         def CmdGen(target, source, env, for_signature):
583             assert for_signature
584             return "%s %s" % \
585                    (env["foo"], env["bar"])
586
587         # The number 1 is there to make sure all args get converted to strings.
588         a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar",
589                                         "$)", "|", "$baz", 1])
590         c = a.get_raw_contents(target=[], source=[],
591                                env=Environment(foo = 'FFF', bar = 'BBB',
592                                                baz = CmdGen))
593         assert c == "| $( FFF | BBB $) | FFF BBB 1", c
594
595         # We've discusssed using the real target and source names in a
596         # CommandAction's signature contents.  This would have have the
597         # advantage of recompiling when a file's name changes (keeping
598         # debug info current), but it would currently break repository
599         # logic that will change the file name based on whether the
600         # files come from a repository or locally.  If we ever move to
601         # that scheme, then all of the '__t1__' and '__s6__' file names
602         # in the asserts below would change to 't1' and 's6' and the
603         # like.
604         t = map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6'])
605         s = map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6'])
606         env = Environment()
607
608         a = SCons.Action.CommandAction(["$TARGET"])
609         c = a.get_raw_contents(target=t, source=s, env=env)
610         assert c == "t1", c
611
612         a = SCons.Action.CommandAction(["$TARGETS"])
613         c = a.get_raw_contents(target=t, source=s, env=env)
614         assert c == "t1 t2 t3 t4 t5 t6", c
615
616         a = SCons.Action.CommandAction(["${TARGETS[2]}"])
617         c = a.get_raw_contents(target=t, source=s, env=env)
618         assert c == "t3", c
619
620         a = SCons.Action.CommandAction(["${TARGETS[3:5]}"])
621         c = a.get_raw_contents(target=t, source=s, env=env)
622         assert c == "t4 t5", c
623
624         a = SCons.Action.CommandAction(["$SOURCE"])
625         c = a.get_raw_contents(target=t, source=s, env=env)
626         assert c == "s1", c
627
628         a = SCons.Action.CommandAction(["$SOURCES"])
629         c = a.get_raw_contents(target=t, source=s, env=env)
630         assert c == "s1 s2 s3 s4 s5 s6", c
631
632         a = SCons.Action.CommandAction(["${SOURCES[2]}"])
633         c = a.get_raw_contents(target=t, source=s, env=env)
634         assert c == "s3", c
635
636         a = SCons.Action.CommandAction(["${SOURCES[3:5]}"])
637         c = a.get_raw_contents(target=t, source=s, env=env)
638         assert c == "s4 s5", c
639
640     def test_get_contents(self):
641         """Test fetching the contents of a command Action
642         """
643         def CmdGen(target, source, env, for_signature):
644             assert for_signature
645             return "%s %s" % \
646                    (env["foo"], env["bar"])
647
648         # The number 1 is there to make sure all args get converted to strings.
649         a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar",
650                                         "$)", "|", "$baz", 1])
651         c = a.get_contents(target=[], source=[],
652                            env=Environment(foo = 'FFF', bar = 'BBB',
653                                            baz = CmdGen))
654         assert c == "| | FFF BBB 1", c
655
656         # We've discusssed using the real target and source names in a
657         # CommandAction's signature contents.  This would have have the
658         # advantage of recompiling when a file's name changes (keeping
659         # debug info current), but it would currently break repository
660         # logic that will change the file name based on whether the
661         # files come from a repository or locally.  If we ever move to
662         # that scheme, then all of the '__t1__' and '__s6__' file names
663         # in the asserts below would change to 't1' and 's6' and the
664         # like.
665         t = map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6'])
666         s = map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6'])
667         env = Environment()
668
669         a = SCons.Action.CommandAction(["$TARGET"])
670         c = a.get_contents(target=t, source=s, env=env)
671         assert c == "t1", c
672
673         a = SCons.Action.CommandAction(["$TARGETS"])
674         c = a.get_contents(target=t, source=s, env=env)
675         assert c == "t1 t2 t3 t4 t5 t6", c
676
677         a = SCons.Action.CommandAction(["${TARGETS[2]}"])
678         c = a.get_contents(target=t, source=s, env=env)
679         assert c == "t3", c
680
681         a = SCons.Action.CommandAction(["${TARGETS[3:5]}"])
682         c = a.get_contents(target=t, source=s, env=env)
683         assert c == "t4 t5", c
684
685         a = SCons.Action.CommandAction(["$SOURCE"])
686         c = a.get_contents(target=t, source=s, env=env)
687         assert c == "s1", c
688
689         a = SCons.Action.CommandAction(["$SOURCES"])
690         c = a.get_contents(target=t, source=s, env=env)
691         assert c == "s1 s2 s3 s4 s5 s6", c
692
693         a = SCons.Action.CommandAction(["${SOURCES[2]}"])
694         c = a.get_contents(target=t, source=s, env=env)
695         assert c == "s3", c
696
697         a = SCons.Action.CommandAction(["${SOURCES[3:5]}"])
698         c = a.get_contents(target=t, source=s, env=env)
699         assert c == "s4 s5", c
700
701 class CommandGeneratorActionTestCase(unittest.TestCase):
702
703     def test_init(self):
704         """Test creation of a command generator Action
705         """
706         def f(target, source, env):
707             pass
708         a = SCons.Action.CommandGeneratorAction(f)
709         assert a.generator == f
710
711     def test_execute(self):
712         """Test executing a command generator Action
713         """
714
715         def f(target, source, env, for_signature, self=self):
716             dummy = env['dummy']
717             self.dummy = dummy
718             s = env.subst("$FOO")
719             assert s == 'foo baz\nbar ack', s
720             return "$FOO"
721         def func_action(target, source, env, self=self):
722             dummy=env['dummy']
723             s = env.subst('$foo')
724             assert s == 'bar', s
725             self.dummy=dummy
726         def f2(target, source, env, for_signature, f=func_action):
727             return f
728         def ch(sh, escape, cmd, args, env, self=self):
729             self.cmd.append(cmd)
730             self.args.append(args)
731
732         a = SCons.Action.CommandGeneratorAction(f)
733         self.dummy = 0
734         self.cmd = []
735         self.args = []
736         a([], [], env=Environment(FOO = 'foo baz\nbar ack',
737                                           dummy = 1,
738                                           SPAWN = ch))
739         assert self.dummy == 1, self.dummy
740         assert self.cmd == ['foo', 'bar'], self.cmd
741         assert self.args == [[ 'foo', 'baz' ], [ 'bar', 'ack' ]], self.args
742
743         b = SCons.Action.CommandGeneratorAction(f2)
744         self.dummy = 0
745         b(target=[], source=[], env=Environment(foo =  'bar',
746                                                         dummy =  2 ))
747         assert self.dummy==2, self.dummy
748         del self.dummy
749
750         class DummyFile:
751             def __init__(self, t):
752                 self.t = t
753             def rfile(self):
754                 self.t.rfile_called = 1
755                 return self
756             def get_subst_proxy(self):
757                 return self
758         def f3(target, source, env, for_signature):
759             return ''
760         c = SCons.Action.CommandGeneratorAction(f3)
761         c(target=[], source=DummyFile(self), env=Environment())
762         assert self.rfile_called
763
764     def test_get_contents(self):
765         """Test fetching the contents of a command generator Action
766         """
767         def f(target, source, env, for_signature):
768             foo = env['foo']
769             bar = env['bar']
770             assert for_signature, for_signature
771             return [["guux", foo, "$(", "$ignore", "$)", bar,
772                      '${test("$( foo $bar $)")}' ]]
773
774         def test(mystr):
775             assert mystr == "$( foo $bar $)", mystr
776             return "test"
777
778         a = SCons.Action.CommandGeneratorAction(f)
779         c = a.get_contents(target=[], source=[],
780                            env=Environment(foo = 'FFF', bar =  'BBB',
781                                            ignore = 'foo', test=test))
782         assert c == "guux FFF BBB test", c
783
784
785 class FunctionActionTestCase(unittest.TestCase):
786
787     def test_init(self):
788         """Test creation of a function Action
789         """
790         def func1():
791             pass
792         def func2():
793             pass
794         def func3():
795             pass
796         def func4():
797             pass
798
799         a = SCons.Action.FunctionAction(func1)
800         assert a.execfunction == func1, a.execfunction
801         assert isinstance(a.strfunction, types.FunctionType)
802
803         a = SCons.Action.FunctionAction(func2, strfunction=func3)
804         assert a.execfunction == func2, a.execfunction
805         assert a.strfunction == func3, a.strfunction
806
807         a = SCons.Action.FunctionAction(func3, func4)
808         assert a.execfunction == func3, a.execfunction
809         assert a.strfunction == func4, a.strfunction
810
811         a = SCons.Action.FunctionAction(func4, None)
812         assert a.execfunction == func4, a.execfunction
813         assert a.strfunction is None, a.strfunction
814
815     def test_execute(self):
816         """Test executing a function Action
817         """
818         self.inc = 0
819         def f(target, source, env):
820             s = env['s']
821             s.inc = s.inc + 1
822             s.target = target
823             s.source=source
824             assert env.subst("$BAR") == 'foo bar', env.subst("$BAR")
825             return 0
826         a = SCons.Action.FunctionAction(f)
827         a(target=1, source=2, env=Environment(BAR = 'foo bar',
828                                                       s = self))
829         assert self.inc == 1, self.inc
830         assert self.source == [2], self.source
831         assert self.target == [1], self.target
832
833         global count
834         count = 0
835         def function1(target, source, env):
836             global count
837             count = count + 1
838             for t in target:
839                 open(t, 'w').write("function1\n")
840             return 1
841
842         act = SCons.Action.FunctionAction(function1)
843         r = None
844         try:
845             r = act(target = [outfile, outfile2], source=[], env=Environment())
846         except SCons.Errors.BuildError:
847             pass
848         assert r == 1
849         assert count == 1
850         c = test.read(outfile, 'r')
851         assert c == "function1\n", c
852         c = test.read(outfile2, 'r')
853         assert c == "function1\n", c
854
855         class class1a:
856             def __init__(self, target, source, env):
857                 open(env['out'], 'w').write("class1a\n")
858
859         act = SCons.Action.FunctionAction(class1a)
860         r = act([], [], Environment(out = outfile))
861         assert r.__class__ == class1a
862         c = test.read(outfile, 'r')
863         assert c == "class1a\n", c
864
865         class class1b:
866             def __call__(self, target, source, env):
867                 open(env['out'], 'w').write("class1b\n")
868                 return 2
869
870         act = SCons.Action.FunctionAction(class1b())
871         r = act([], [], Environment(out = outfile))
872         assert r == 2
873         c = test.read(outfile, 'r')
874         assert c == "class1b\n", c
875
876         def build_it(target, source, env, self=self):
877             self.build_it = 1
878             return 0
879         def string_it(target, source, env, self=self):
880             self.string_it = 1
881             return None
882         act = SCons.Action.FunctionAction(build_it, string_it)
883         r = act([], [], Environment())
884         assert r == 0, r
885         assert self.build_it
886         assert self.string_it
887
888     def test_get_contents(self):
889         """Test fetching the contents of a function Action
890         """
891         a = SCons.Action.FunctionAction(Func)
892         c = a.get_contents(target=[], source=[], env=Environment())
893         assert c == "\177\036\000\177\037\000d\000\000S", repr(c)
894
895         a = SCons.Action.FunctionAction(Func, varlist=['XYZ'])
896         c = a.get_contents(target=[], source=[], env=Environment())
897         assert c == "\177\036\000\177\037\000d\000\000S", repr(c)
898         c = a.get_contents(target=[], source=[], env=Environment(XYZ = 'foo'))
899         assert c == "\177\036\000\177\037\000d\000\000Sfoo", repr(c)
900
901 class ListActionTestCase(unittest.TestCase):
902
903     def test_init(self):
904         """Test creation of a list of subsidiary Actions
905         """
906         def func():
907             pass
908         a = SCons.Action.ListAction(["x", func, ["y", "z"]])
909         assert isinstance(a.list[0], SCons.Action.CommandAction)
910         assert isinstance(a.list[1], SCons.Action.FunctionAction)
911         assert isinstance(a.list[2], SCons.Action.ListAction)
912         assert a.list[2].list[0].cmd_list == 'y'
913
914     def test_get_actions(self):
915         """Test the get_actions() method for ListActions
916         """
917         a = SCons.Action.ListAction(["x", "y"])
918         l = a.get_actions()
919         assert len(l) == 2, l
920         assert isinstance(l[0], SCons.Action.CommandAction), l[0]
921         g = l[0].get_actions()
922         assert g == [l[0]], g
923         assert isinstance(l[1], SCons.Action.CommandAction), l[1]
924         g = l[1].get_actions()
925         assert g == [l[1]], g
926
927     def test_execute(self):
928         """Test executing a list of subsidiary Actions
929         """
930         self.inc = 0
931         def f(target,source,env):
932             s = env['s']
933             s.inc = s.inc + 1
934         a = SCons.Action.ListAction([f, f, f])
935         a([], [], Environment(s = self))
936         assert self.inc == 3, self.inc
937
938         cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile)
939
940         def function2(target, source, env):
941             open(env['out'], 'a').write("function2\n")
942             return 0
943
944         class class2a:
945             def __call__(self, target, source, env):
946                 open(env['out'], 'a').write("class2a\n")
947                 return 0
948
949         class class2b:
950             def __init__(self, target, source, env):
951                 open(env['out'], 'a').write("class2b\n")
952         act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b])
953         r = act([], [], Environment(out = outfile))
954         assert r.__class__ == class2b
955         c = test.read(outfile, 'r')
956         assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
957
958     def test_get_contents(self):
959         """Test fetching the contents of a list of subsidiary Actions
960         """
961         self.foo=0
962         def gen(target, source, env, for_signature):
963             s = env['s']
964             s.foo=1
965             return "y"
966         a = SCons.Action.ListAction(["x",
967                                      SCons.Action.CommandGenerator(gen),
968                                      "z"])
969         c = a.get_contents(target=[], source=[], env=Environment(s = self))
970         assert self.foo==1, self.foo
971         assert c == "xyz", c
972
973 class LazyActionTestCase(unittest.TestCase):
974     def test_init(self):
975         """Test creation of a lazy-evaluation Action
976         """
977         # Environment variable references should create a special
978         # type of CommandGeneratorAction that lazily evaluates the
979         # variable.
980         a9 = SCons.Action.Action('$FOO')
981         assert isinstance(a9, SCons.Action.CommandGeneratorAction), a9
982         assert a9.generator.var == 'FOO', a9.generator.var
983
984         a10 = SCons.Action.Action('${FOO}')
985         assert isinstance(a9, SCons.Action.CommandGeneratorAction), a10
986         assert a10.generator.var == 'FOO', a10.generator.var
987
988     def test_execute(self):
989         """Test executing a lazy-evaluation Action
990         """
991         def f(target, source, env):
992             s = env['s']
993             s.test=1
994             return 0
995         a = SCons.Action.Action('$BAR')
996         a([], [], env=Environment(BAR = f, s = self))
997         assert self.test == 1, self.test
998
999     def test_get_contents(self):
1000         """Test fetching the contents of a lazy-evaluation Action
1001         """
1002         a = SCons.Action.Action("${FOO}")
1003         c = a.get_contents(target=[], source=[],
1004                            env = Environment(FOO = [["This", "is", "$(", "$a", "$)", "test"]]))
1005         assert c == "This is test", c
1006
1007
1008 if __name__ == "__main__":
1009     suite = unittest.TestSuite()
1010     tclasses = [ ActionTestCase,
1011                  ActionBaseTestCase,
1012                  CommandActionTestCase,
1013                  CommandGeneratorActionTestCase,
1014                  FunctionActionTestCase,
1015                  ListActionTestCase,
1016                  LazyActionTestCase ]
1017     for tclass in tclasses:
1018         names = unittest.getTestCaseNames(tclass, 'test_')
1019         suite.addTests(map(tclass, names))
1020     if not unittest.TextTestRunner().run(suite).wasSuccessful():
1021         sys.exit(1)