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