Followon to PR #4348: more bool fixes
[scons.git] / SCons / ActionTests.py
blob07fa297d96e95559ec177b143b60fcc07441e6f0
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 # Define a null function and a null class for use as builder actions.
26 # Where these are defined in the file seems to affect their byte-code
27 # contents, so try to minimize changes by defining them here, before we
28 # even import anything.
30 def GlobalFunc() -> None:
31 pass
34 class GlobalActFunc:
35 def __call__(self) -> None:
36 pass
39 import io
40 import os
41 import sys
42 import types
43 import unittest
44 import subprocess
46 import SCons.Action
47 import SCons.Environment
48 import SCons.Errors
50 import TestCmd
52 # Initial setup of the common environment for all tests,
53 # a temporary working directory containing a
54 # script for writing arguments to an output file.
56 # We don't do this as a setUp() method because it's
57 # unnecessary to create a separate directory and script
58 # for each test, they can just use the one.
59 test = TestCmd.TestCmd(workdir='')
61 test.write('act.py', """\
62 import os, string, sys
64 with open(sys.argv[1], 'w') as f:
65 f.write("act.py: '" + "' '".join(sys.argv[2:]) + "'\\n")
66 try:
67 if sys.argv[3]:
68 f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n")
69 except:
70 pass
72 if 'ACTPY_PIPE' in os.environ:
73 if 'PIPE_STDOUT_FILE' in os.environ:
74 with open(os.environ['PIPE_STDOUT_FILE'], 'r') as f:
75 stdout_msg = f.read()
76 else:
77 stdout_msg = "act.py: stdout: executed act.py %s\\n" % ' '.join(sys.argv[1:])
78 sys.stdout.write(stdout_msg)
79 if 'PIPE_STDERR_FILE' in os.environ:
80 with open(os.environ['PIPE_STDERR_FILE'], 'r') as f:
81 stderr_msg = f.read()
82 else:
83 stderr_msg = "act.py: stderr: executed act.py %s\\n" % ' '.join(sys.argv[1:])
84 sys.stderr.write(stderr_msg)
85 sys.exit(0)
86 """)
88 test.write('exit.py', """\
89 import sys
90 sys.exit(int(sys.argv[1]))
91 """)
93 act_py = test.workpath('act.py')
94 exit_py = test.workpath('exit.py')
96 outfile = test.workpath('outfile')
97 outfile2 = test.workpath('outfile2')
98 pipe_file = test.workpath('pipe.out')
100 scons_env = SCons.Environment.Environment()
102 # Capture all the stuff the Actions will print,
103 # so it doesn't clutter the output.
104 sys.stdout = io.StringIO()
107 class CmdStringHolder:
108 def __init__(self, cmd, literal=None) -> None:
109 self.data = str(cmd)
110 self.literal = literal
112 def is_literal(self) -> bool:
113 return self.literal
115 def escape(self, escape_func):
116 """Escape the string with the supplied function.
118 The function is expected to take an arbitrary string, then
119 return it with all special characters escaped and ready
120 for passing to the command interpreter.
122 After calling this function, the next call to str() will
123 return the escaped string.
126 if self.is_literal():
127 return escape_func(self.data)
128 elif ' ' in self.data or '\t' in self.data:
129 return '"%s"' % self.data
130 else:
131 return self.data
134 class Environment:
135 def __init__(self, **kw) -> None:
136 self.d = {}
137 self.d['SHELL'] = scons_env['SHELL']
138 self.d['SPAWN'] = scons_env['SPAWN']
139 self.d['PSPAWN'] = scons_env['PSPAWN']
140 self.d['ESCAPE'] = scons_env['ESCAPE']
141 for k, v in kw.items():
142 self.d[k] = v
144 # Just use the underlying scons_subst*() utility methods.
145 def subst(self, strSubst, raw: int=0, target=[], source=[], conv=None, overrides: bool=False):
146 return SCons.Subst.scons_subst(strSubst, self, raw,
147 target, source, self.d, conv=conv, overrides=overrides)
149 subst_target_source = subst
151 def subst_list(self, strSubst, raw: int=0, target=[], source=[], conv=None, overrides: bool=False):
152 return SCons.Subst.scons_subst_list(strSubst, self, raw,
153 target, source, self.d, conv=conv, overrides=overrides)
155 def __getitem__(self, item):
156 return self.d[item]
158 def __setitem__(self, item, value) -> None:
159 self.d[item] = value
161 def __contains__(self, key) -> bool:
162 return key in self.d
164 def get(self, key, value=None):
165 return self.d.get(key, value)
167 def items(self):
168 return list(self.d.items())
170 def Dictionary(self):
171 return self.d
173 def Clone(self, **kw):
174 res = Environment()
175 res.d = SCons.Util.semi_deepcopy(self.d)
176 for k, v in kw.items():
177 res.d[k] = v
178 return res
180 def sig_dict(self):
181 d = {}
182 for k, v in self.items(): d[k] = v
183 d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
184 d['TARGET'] = d['TARGETS'][0]
185 d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
186 d['SOURCE'] = d['SOURCES'][0]
187 return d
190 class DummyNode:
191 def __init__(self, name) -> None:
192 self.name = name
194 def str_for_display(self):
195 return '"' + self.name + '"'
197 def __str__(self) -> str:
198 return self.name
200 def rfile(self):
201 return self
203 def get_subst_proxy(self):
204 return self
207 if os.name == 'java':
208 python = os.path.join(sys.prefix, 'jython')
209 else:
210 python = os.environ.get('python_executable', sys.executable)
211 _python_ = test.escape(python)
213 _null = SCons.Action._null
216 def test_varlist(pos_call, str_call, cmd, cmdstrfunc, **kw) -> None:
217 def call_action(a, pos_call=pos_call, str_call=str_call, kw=kw):
218 a = SCons.Action.Action(*a, **kw)
219 # returned object must provide these entry points
220 assert hasattr(a, '__call__')
221 assert hasattr(a, 'get_contents')
222 assert hasattr(a, 'genstring')
223 pos_call(a)
224 str_call(a)
225 return a
227 a = call_action((cmd, cmdstrfunc))
228 assert a.varlist == (), a.varlist
230 a = call_action((cmd, cmdstrfunc, 'foo'))
231 assert a.varlist == ('foo',), a.varlist
233 a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c'))
234 assert a.varlist == ('a', 'b', 'c'), a.varlist
236 a = call_action((cmd, cmdstrfunc, ['a', 'b', 'c']))
237 assert a.varlist == ('a', 'b', 'c'), a.varlist
239 kw['varlist'] = 'foo'
240 a = call_action((cmd, cmdstrfunc))
241 assert a.varlist == ('foo',), a.varlist
243 kw['varlist'] = ['x', 'y', 'z']
244 a = call_action((cmd, cmdstrfunc))
245 assert a.varlist == ('x', 'y', 'z'), a.varlist
247 a = call_action((cmd, cmdstrfunc, 'foo'))
248 assert a.varlist == ('foo', 'x', 'y', 'z'), a.varlist
250 a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c'))
251 assert a.varlist == ('a', 'b', 'c', 'x', 'y', 'z'), a.varlist
254 def test_positional_args(pos_callback, cmd, **kw):
255 """Test that Action returns the expected type and positional args work."""
257 act = SCons.Action.Action(cmd, **kw)
258 pos_callback(act)
259 assert act.varlist == (), act.varlist
261 if not isinstance(act, SCons.Action._ActionAction):
262 # only valid cmdstrfunc is None
263 def none(a) -> None:
264 pass
266 test_varlist(pos_callback, none, cmd, None, **kw)
267 else:
268 # _ActionAction should have set these
269 assert hasattr(act, 'strfunction')
270 assert act.cmdstr is _null, act.cmdstr
271 assert act.presub is _null, act.presub
272 assert act.chdir is None, act.chdir
273 assert act.exitstatfunc is SCons.Action.default_exitstatfunc, \
274 act.exitstatfunc
276 def cmdstr(a) -> None:
277 assert hasattr(a, 'strfunction')
278 assert a.cmdstr == 'cmdstr', a.cmdstr
280 test_varlist(pos_callback, cmdstr, cmd, 'cmdstr', **kw)
282 def fun() -> None:
283 pass
285 def strfun(a, fun=fun) -> None:
286 assert a.strfunction is fun, a.strfunction
287 assert a.cmdstr == _null, a.cmdstr
289 test_varlist(pos_callback, strfun, cmd, fun, **kw)
291 def none(a) -> None:
292 assert hasattr(a, 'strfunction')
293 assert a.cmdstr is None, a.cmdstr
295 test_varlist(pos_callback, none, cmd, None, **kw)
297 # Test handling of bad cmdstrfunc arguments
298 try:
299 a = SCons.Action.Action(cmd, [], **kw)
300 except SCons.Errors.UserError as e:
301 s = str(e)
302 m = 'Invalid command display variable'
303 assert s.find(m) != -1, 'Unexpected string: %s' % s
304 else:
305 raise Exception("did not catch expected UserError")
307 return act
310 class ActionTestCase(unittest.TestCase):
311 """Test the Action() factory function"""
313 def test_FunctionAction(self) -> None:
314 """Test the Action() factory's creation of FunctionAction objects."""
316 def foo() -> None:
317 pass
319 def func_action(a, foo=foo) -> None:
320 assert isinstance(a, SCons.Action.FunctionAction), a
321 assert a.execfunction == foo, a.execfunction
323 test_positional_args(func_action, foo)
324 # a singleton list returns the contained action
325 test_positional_args(func_action, [foo])
327 def test_CommandAction(self) -> None:
328 """Test the Action() factory's creation of CommandAction objects."""
330 def cmd_action(a) -> None:
331 assert isinstance(a, SCons.Action.CommandAction), a
332 assert a.cmd_list == "string", a.cmd_list
334 test_positional_args(cmd_action, "string")
335 # a singleton list returns the contained action
336 test_positional_args(cmd_action, ["string"])
338 def line_action(a) -> None:
339 assert isinstance(a, SCons.Action.CommandAction), a
340 assert a.cmd_list == ["explicit", "command", "line"], a.cmd_list
342 test_positional_args(line_action, [["explicit", "command", "line"]])
344 def test_ListAction(self) -> None:
345 """Test the Action() factory's creation of ListAction objects."""
347 a1 = SCons.Action.Action(["x", "y", "z", ["a", "b", "c"]])
348 assert isinstance(a1, SCons.Action.ListAction), a1
349 assert a1.varlist == (), a1.varlist
350 assert isinstance(a1.list[0], SCons.Action.CommandAction), a1.list[0]
351 assert a1.list[0].cmd_list == "x", a1.list[0].cmd_list
352 assert isinstance(a1.list[1], SCons.Action.CommandAction), a1.list[1]
353 assert a1.list[1].cmd_list == "y", a1.list[1].cmd_list
354 assert isinstance(a1.list[2], SCons.Action.CommandAction), a1.list[2]
355 assert a1.list[2].cmd_list == "z", a1.list[2].cmd_list
356 assert isinstance(a1.list[3], SCons.Action.CommandAction), a1.list[3]
357 assert a1.list[3].cmd_list == ["a", "b", "c"], a1.list[3].cmd_list
359 a2 = SCons.Action.Action("x\ny\nz")
360 assert isinstance(a2, SCons.Action.ListAction), a2
361 assert a2.varlist == (), a2.varlist
362 assert isinstance(a2.list[0], SCons.Action.CommandAction), a2.list[0]
363 assert a2.list[0].cmd_list == "x", a2.list[0].cmd_list
364 assert isinstance(a2.list[1], SCons.Action.CommandAction), a2.list[1]
365 assert a2.list[1].cmd_list == "y", a2.list[1].cmd_list
366 assert isinstance(a2.list[2], SCons.Action.CommandAction), a2.list[2]
367 assert a2.list[2].cmd_list == "z", a2.list[2].cmd_list
369 def foo() -> None:
370 pass
372 a3 = SCons.Action.Action(["x", foo, "z"])
373 assert isinstance(a3, SCons.Action.ListAction), a3
374 assert a3.varlist == (), a3.varlist
375 assert isinstance(a3.list[0], SCons.Action.CommandAction), a3.list[0]
376 assert a3.list[0].cmd_list == "x", a3.list[0].cmd_list
377 assert isinstance(a3.list[1], SCons.Action.FunctionAction), a3.list[1]
378 assert a3.list[1].execfunction == foo, a3.list[1].execfunction
379 assert isinstance(a3.list[2], SCons.Action.CommandAction), a3.list[2]
380 assert a3.list[2].cmd_list == "z", a3.list[2].cmd_list
382 a4 = SCons.Action.Action(["x", "y"], strfunction=foo)
383 assert isinstance(a4, SCons.Action.ListAction), a4
384 assert a4.varlist == (), a4.varlist
385 assert isinstance(a4.list[0], SCons.Action.CommandAction), a4.list[0]
386 assert a4.list[0].cmd_list == "x", a4.list[0].cmd_list
387 assert a4.list[0].strfunction == foo, a4.list[0].strfunction
388 assert isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1]
389 assert a4.list[1].cmd_list == "y", a4.list[1].cmd_list
390 assert a4.list[1].strfunction == foo, a4.list[1].strfunction
392 a5 = SCons.Action.Action("x\ny", strfunction=foo)
393 assert isinstance(a5, SCons.Action.ListAction), a5
394 assert a5.varlist == (), a5.varlist
395 assert isinstance(a5.list[0], SCons.Action.CommandAction), a5.list[0]
396 assert a5.list[0].cmd_list == "x", a5.list[0].cmd_list
397 assert a5.list[0].strfunction == foo, a5.list[0].strfunction
398 assert isinstance(a5.list[1], SCons.Action.CommandAction), a5.list[1]
399 assert a5.list[1].cmd_list == "y", a5.list[1].cmd_list
400 assert a5.list[1].strfunction == foo, a5.list[1].strfunction
402 def test_CommandGeneratorAction(self) -> None:
403 """Test the Action factory's creation of CommandGeneratorAction objects."""
405 def foo() -> None: pass
407 def gen_action(a, foo=foo) -> None:
408 assert isinstance(a, SCons.Action.CommandGeneratorAction), a
409 assert a.generator is foo, a.generator
411 test_positional_args(gen_action, foo, generator=1)
413 def test_LazyCmdGeneratorAction(self) -> None:
414 """Test the Action factory's creation of lazy CommandGeneratorAction objects."""
416 def lazy_action(a) -> None:
417 assert isinstance(a, SCons.Action.LazyAction), a
418 assert a.var == "FOO", a.var
419 assert a.cmd_list == "${FOO}", a.cmd_list
421 test_positional_args(lazy_action, "$FOO")
422 test_positional_args(lazy_action, "${FOO}")
424 def test_no_action(self) -> None:
425 """Test when the Action() factory can't create an action object."""
427 try:
428 a5 = SCons.Action.Action(1)
429 except TypeError:
430 pass
431 else:
432 assert 0, "Should have thrown a TypeError creating Action from an int."
434 def test_reentrance(self) -> None:
435 """Test the Action factory when the action is already an Action object."""
437 a1 = SCons.Action.Action("foo")
438 a2 = SCons.Action.Action(a1)
439 assert a2 is a1, a2
442 class _ActionActionTestCase(unittest.TestCase):
444 def test__init__(self) -> None:
445 """Test creation of _ActionAction objects."""
447 def func1() -> None:
448 pass
450 def func2() -> None:
451 pass
453 def func3() -> None:
454 pass
456 a = SCons.Action._ActionAction()
457 assert not hasattr(a, 'strfunction')
458 assert a.cmdstr is _null, a.cmdstr
459 assert a.varlist == (), a.varlist
460 assert a.presub is _null, a.presub
461 assert a.chdir is None, a.chdir
462 assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc
464 assert SCons.Action._ActionAction(kwarg=1)
465 assert not hasattr(a, 'kwarg')
466 assert not hasattr(a, 'strfunction')
467 assert a.cmdstr is _null, a.cmdstr
468 assert a.varlist == (), a.varlist
469 assert a.presub is _null, a.presub
470 assert a.chdir is None, a.chdir
471 assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc
473 a = SCons.Action._ActionAction(strfunction=func1)
474 assert a.strfunction is func1, a.strfunction
476 a = SCons.Action._ActionAction(strfunction=None)
477 assert not hasattr(a, 'strfunction')
478 assert a.cmdstr is None, a.cmdstr
480 a = SCons.Action._ActionAction(cmdstr='cmdstr')
481 assert not hasattr(a, 'strfunction')
482 assert a.cmdstr == 'cmdstr', a.cmdstr
484 a = SCons.Action._ActionAction(cmdstr=None)
485 assert not hasattr(a, 'strfunction')
486 assert a.cmdstr is None, a.cmdstr
488 t = ('a', 'b', 'c')
489 a = SCons.Action._ActionAction(varlist=t)
490 assert a.varlist == t, a.varlist
492 a = SCons.Action._ActionAction(presub=func1)
493 assert a.presub is func1, a.presub
495 a = SCons.Action._ActionAction(chdir=1)
496 assert a.chdir == 1, a.chdir
498 a = SCons.Action._ActionAction(exitstatfunc=func1)
499 assert a.exitstatfunc is func1, a.exitstatfunc
501 a = SCons.Action._ActionAction(
502 # alphabetical order ...
503 chdir='x',
504 cmdstr='cmdstr',
505 exitstatfunc=func3,
506 presub=func2,
507 strfunction=func1,
508 varlist=t,
510 assert a.chdir == 'x', a.chdir
511 assert a.cmdstr == 'cmdstr', a.cmdstr
512 assert a.exitstatfunc is func3, a.exitstatfunc
513 assert a.presub is func2, a.presub
514 assert a.strfunction is func1, a.strfunction
515 assert a.varlist is t, a.varlist
517 def test_dup_keywords(self):
518 """Test handling of both cmdstr and strfunction arguments."""
520 def func() -> None:
521 pass
523 try:
524 a = SCons.Action.Action('foo', cmdstr='string', strfunction=func)
525 except SCons.Errors.UserError as e:
526 s = str(e)
527 m = 'Cannot have both strfunction and cmdstr args to Action()'
528 assert s.find(m) != -1, 'Unexpected string: %s' % s
529 else:
530 raise Exception("did not catch expected UserError")
532 def test___cmp__(self) -> None:
533 """Test Action comparison."""
535 a1 = SCons.Action.Action("x")
536 a2 = SCons.Action.Action("x")
537 assert a1 == a2
538 a3 = SCons.Action.Action("y")
539 assert a1 != a3
540 assert a2 != a3
542 def test_print_cmd_lines(self) -> None:
543 """Test the print_cmd_lines() method."""
545 save_stdout = sys.stdout
547 try:
548 def execfunc(target, source, env) -> None:
549 pass
551 a = SCons.Action.Action(execfunc)
553 sio = io.StringIO()
554 sys.stdout = sio
555 a.print_cmd_line("foo bar", None, None, None)
556 s = sio.getvalue()
557 assert s == "foo bar\n", s
559 finally:
560 sys.stdout = save_stdout
562 def test___call__(self) -> None:
563 """Test calling an Action."""
565 save_stdout = sys.stdout
567 save_print_actions = SCons.Action.print_actions
568 save_print_actions_presub = SCons.Action.print_actions_presub
569 save_execute_actions = SCons.Action.execute_actions
570 # SCons.Action.print_actions = 0
572 test = TestCmd.TestCmd(workdir='')
573 test.subdir('sub', 'xyz')
574 os.chdir(test.workpath())
576 try:
577 env = Environment()
579 def execfunc(target, source, env) -> int:
580 assert isinstance(target, list), type(target)
581 assert isinstance(source, list), type(source)
582 return 7
584 a = SCons.Action.Action(execfunc)
586 def firstfunc(target, source, env) -> int:
587 assert isinstance(target, list), type(target)
588 assert isinstance(source, list), type(source)
589 return 0
591 def lastfunc(target, source, env) -> int:
592 assert isinstance(target, list), type(target)
593 assert isinstance(source, list), type(source)
594 return 9
596 b = SCons.Action.Action([firstfunc, execfunc, lastfunc])
598 sio = io.StringIO()
599 sys.stdout = sio
600 result = a("out", "in", env)
601 assert result.status == 7, result
602 s = sio.getvalue()
603 assert s == "execfunc(['out'], ['in'])\n", s
605 a.chdir = 'xyz'
606 expect = "os.chdir(%s)\nexecfunc(['out'], ['in'])\nos.chdir(%s)\n"
608 sio = io.StringIO()
609 sys.stdout = sio
610 result = a("out", "in", env)
611 assert result.status == 7, result.status
612 s = sio.getvalue()
613 assert s == expect % (repr('xyz'), repr(test.workpath())), s
615 sio = io.StringIO()
616 sys.stdout = sio
617 result = a("out", "in", env, chdir='sub')
618 assert result.status == 7, result.status
619 s = sio.getvalue()
620 assert s == expect % (repr('sub'), repr(test.workpath())), s
622 a.chdir = None
624 sio = io.StringIO()
625 sys.stdout = sio
626 result = b("out", "in", env)
627 assert result.status == 7, result.status
628 s = sio.getvalue()
629 assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\n", s
631 SCons.Action.execute_actions = 0
633 sio = io.StringIO()
634 sys.stdout = sio
635 result = a("out", "in", env)
636 assert result == 0, result
637 s = sio.getvalue()
638 assert s == "execfunc(['out'], ['in'])\n", s
640 sio = io.StringIO()
641 sys.stdout = sio
642 result = b("out", "in", env)
643 assert result == 0, result
644 s = sio.getvalue()
645 assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\nlastfunc(['out'], ['in'])\n", s
647 SCons.Action.print_actions_presub = 1
648 SCons.Action.execute_actions = 1
650 sio = io.StringIO()
651 sys.stdout = sio
652 result = a("out", "in", env)
653 assert result.status == 7, result.status
654 s = sio.getvalue()
655 assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
657 sio = io.StringIO()
658 sys.stdout = sio
659 result = a("out", "in", env, presub=0)
660 assert result.status == 7, result.status
661 s = sio.getvalue()
662 assert s == "execfunc(['out'], ['in'])\n", s
664 sio = io.StringIO()
665 sys.stdout = sio
666 result = a("out", "in", env, presub=1)
667 assert result.status == 7, result.status
668 s = sio.getvalue()
669 assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
671 sio = io.StringIO()
672 sys.stdout = sio
673 result = b(["out"], "in", env, presub=1)
674 assert result.status == 7, result.status
675 s = sio.getvalue()
676 assert s == "Building out with action:\n firstfunc(target, source, env)\nfirstfunc(['out'], ['in'])\nBuilding out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
678 sio = io.StringIO()
679 sys.stdout = sio
680 result = b(["out", "list"], "in", env, presub=1)
681 assert result.status == 7, result.status
682 s = sio.getvalue()
683 assert s == "Building out and list with action:\n firstfunc(target, source, env)\nfirstfunc(['out', 'list'], ['in'])\nBuilding out and list with action:\n execfunc(target, source, env)\nexecfunc(['out', 'list'], ['in'])\n", s
685 a2 = SCons.Action.Action(execfunc)
687 sio = io.StringIO()
688 sys.stdout = sio
689 result = a2("out", "in", env)
690 assert result.status == 7, result.status
691 s = sio.getvalue()
692 assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
694 sio = io.StringIO()
695 sys.stdout = sio
696 result = a2("out", "in", env, presub=0)
697 assert result.status == 7, result.status
698 s = sio.getvalue()
699 assert s == "execfunc(['out'], ['in'])\n", s
701 SCons.Action.execute_actions = 0
703 sio = io.StringIO()
704 sys.stdout = sio
705 result = a2("out", "in", env, presub=0)
706 assert result == 0, result
707 s = sio.getvalue()
708 assert s == "execfunc(['out'], ['in'])\n", s
710 sio = io.StringIO()
711 sys.stdout = sio
712 result = a("out", "in", env, presub=0, execute=1, show=0)
713 assert result.status == 7, result.status
714 s = sio.getvalue()
715 assert s == '', s
717 sys.stdout = save_stdout
718 exitstatfunc_result = []
720 def exitstatfunc(stat, result=exitstatfunc_result):
721 result.append(stat)
722 return stat
724 result = a("out", "in", env, exitstatfunc=exitstatfunc)
725 assert result == 0, result
726 assert exitstatfunc_result == [], exitstatfunc_result
728 result = a("out", "in", env, execute=1, exitstatfunc=exitstatfunc)
729 assert result.status == 7, result.status
730 assert exitstatfunc_result == [7], exitstatfunc_result
732 SCons.Action.execute_actions = 1
734 result = []
736 def my_print_cmd_line(s, target, source, env, result=result) -> None:
737 result.append(s)
739 env['PRINT_CMD_LINE_FUNC'] = my_print_cmd_line
740 a("output", "input", env)
741 assert result == ["execfunc(['output'], ['input'])"], result
744 finally:
745 sys.stdout = save_stdout
746 SCons.Action.print_actions = save_print_actions
747 SCons.Action.print_actions_presub = save_print_actions_presub
748 SCons.Action.execute_actions = save_execute_actions
750 def test_presub_lines(self) -> None:
751 """Test the presub_lines() method."""
753 env = Environment()
754 a = SCons.Action.Action("x")
755 s = a.presub_lines(env)
756 assert s == ['x'], s
758 a = SCons.Action.Action(["y", "z"])
759 s = a.presub_lines(env)
760 assert s == ['y', 'z'], s
762 def func() -> None:
763 pass
765 a = SCons.Action.Action(func)
766 s = a.presub_lines(env)
767 assert s == ["func(target, source, env)"], s
769 def gen(target, source, env, for_signature):
770 return 'generat' + env.get('GEN', 'or')
772 a = SCons.Action.Action(gen, generator=1)
773 s = a.presub_lines(env)
774 assert s == ["generator"], s
775 s = a.presub_lines(Environment(GEN='ed'))
776 assert s == ["generated"], s
778 a = SCons.Action.Action("$ACT")
779 s = a.presub_lines(env)
780 assert s == [''], s
781 s = a.presub_lines(Environment(ACT='expanded action'))
782 assert s == ['expanded action'], s
784 def test_add(self) -> None:
785 """Test adding Actions to stuff."""
787 # Adding actions to other Actions or to stuff that can
788 # be converted into an Action should produce a ListAction
789 # containing all the Actions.
790 def bar():
791 return None
793 baz = SCons.Action.Action(bar, generator=1)
794 act1 = SCons.Action.Action('foo bar')
795 act2 = SCons.Action.Action(['foo', bar])
797 sum = act1 + act2
798 assert isinstance(sum, SCons.Action.ListAction), str(sum)
799 assert len(sum.list) == 3, len(sum.list)
800 assert [isinstance(x, SCons.Action.ActionBase) for x in sum.list] == [1, 1, 1]
802 sum = act1 + act1
803 assert isinstance(sum, SCons.Action.ListAction), str(sum)
804 assert len(sum.list) == 2, len(sum.list)
806 sum = act2 + act2
807 assert isinstance(sum, SCons.Action.ListAction), str(sum)
808 assert len(sum.list) == 4, len(sum.list)
810 # Should also be able to add command generators to each other
811 # or to actions
812 sum = baz + baz
813 assert isinstance(sum, SCons.Action.ListAction), str(sum)
814 assert len(sum.list) == 2, len(sum.list)
816 sum = baz + act1
817 assert isinstance(sum, SCons.Action.ListAction), str(sum)
818 assert len(sum.list) == 2, len(sum.list)
820 sum = act2 + baz
821 assert isinstance(sum, SCons.Action.ListAction), str(sum)
822 assert len(sum.list) == 3, len(sum.list)
824 # Also should be able to add Actions to anything that can
825 # be converted into an action.
826 sum = act1 + bar
827 assert isinstance(sum, SCons.Action.ListAction), str(sum)
828 assert len(sum.list) == 2, len(sum.list)
829 assert isinstance(sum.list[1], SCons.Action.FunctionAction)
831 sum = 'foo bar' + act2
832 assert isinstance(sum, SCons.Action.ListAction), str(sum)
833 assert len(sum.list) == 3, len(sum.list)
834 assert isinstance(sum.list[0], SCons.Action.CommandAction)
836 sum = ['foo', 'bar'] + act1
837 assert isinstance(sum, SCons.Action.ListAction), str(sum)
838 assert len(sum.list) == 3, sum.list
839 assert isinstance(sum.list[0], SCons.Action.CommandAction)
840 assert isinstance(sum.list[1], SCons.Action.CommandAction)
842 sum = act2 + [baz, bar]
843 assert isinstance(sum, SCons.Action.ListAction), str(sum)
844 assert len(sum.list) == 4, len(sum.list)
845 assert isinstance(sum.list[2], SCons.Action.CommandGeneratorAction)
846 assert isinstance(sum.list[3], SCons.Action.FunctionAction)
848 # OK to add None on either side (should be ignored)
849 sum = act1 + None
850 assert sum == act1
852 sum = None + act1
853 assert sum == act1
855 try:
856 sum = act2 + 1
857 except TypeError:
858 pass
859 else:
860 assert 0, "Should have thrown a TypeError adding to an int."
862 try:
863 sum = 1 + act2
864 except TypeError:
865 pass
866 else:
867 assert 0, "Should have thrown a TypeError adding to an int."
870 class CommandActionTestCase(unittest.TestCase):
872 def test___init__(self) -> None:
873 """Test creation of a command Action."""
875 a = SCons.Action.CommandAction(["xyzzy"])
876 assert a.cmd_list == ["xyzzy"], a.cmd_list
877 assert a.cmdstr is _null, a.cmdstr
879 a = SCons.Action.CommandAction(["abra"], cmdstr="cadabra")
880 assert a.cmd_list == ["abra"], a.cmd_list
881 assert a.cmdstr == "cadabra", a.cmdstr
883 def test___str__(self) -> None:
884 """Test fetching the pre-substitution string for command Actions."""
886 env = Environment()
887 act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
888 s = str(act)
889 assert s == 'xyzzy $TARGET $SOURCE', s
891 act = SCons.Action.CommandAction(['xyzzy',
892 '$TARGET', '$SOURCE',
893 '$TARGETS', '$SOURCES'])
894 s = str(act)
895 assert s == "xyzzy $TARGET $SOURCE $TARGETS $SOURCES", s
897 def test_genstring(self) -> None:
898 """Test the genstring() method for command Actions."""
900 env = Environment()
901 t1 = DummyNode('t1')
902 t2 = DummyNode('t2')
903 s1 = DummyNode('s1')
904 s2 = DummyNode('s2')
905 act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
906 expect = 'xyzzy $TARGET $SOURCE'
907 s = act.genstring([], [], env)
908 assert s == expect, s
909 s = act.genstring([t1], [s1], env)
910 assert s == expect, s
911 s = act.genstring([t1, t2], [s1, s2], env)
912 assert s == expect, s
914 act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
915 expect = 'xyzzy $TARGETS $SOURCES'
916 s = act.genstring([], [], env)
917 assert s == expect, s
918 s = act.genstring([t1], [s1], env)
919 assert s == expect, s
920 s = act.genstring([t1, t2], [s1, s2], env)
921 assert s == expect, s
923 act = SCons.Action.CommandAction(['xyzzy',
924 '$TARGET', '$SOURCE',
925 '$TARGETS', '$SOURCES'])
926 expect = "xyzzy $TARGET $SOURCE $TARGETS $SOURCES"
927 s = act.genstring([], [], env)
928 assert s == expect, s
929 s = act.genstring([t1], [s1], env)
930 assert s == expect, s
931 s = act.genstring([t1, t2], [s1, s2], env)
932 assert s == expect, s
934 def test_strfunction(self) -> None:
935 """Test fetching the string representation of command Actions."""
937 env = Environment()
938 t1 = DummyNode('t1')
939 t2 = DummyNode('t2')
940 s1 = DummyNode('s1')
941 s2 = DummyNode('s2')
942 act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
943 s = act.strfunction([], [], env)
944 assert s == 'xyzzy', s
945 s = act.strfunction([t1], [s1], env)
946 assert s == 'xyzzy t1 s1', s
947 s = act.strfunction([t1, t2], [s1, s2], env)
948 assert s == 'xyzzy t1 s1', s
950 act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE',
951 cmdstr='cmdstr - $SOURCE - $TARGET -')
952 s = act.strfunction([], [], env)
953 assert s == 'cmdstr - - -', s
954 s = act.strfunction([t1], [s1], env)
955 assert s == 'cmdstr - s1 - t1 -', s
956 s = act.strfunction([t1, t2], [s1, s2], env)
957 assert s == 'cmdstr - s1 - t1 -', s
959 act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
960 s = act.strfunction([], [], env)
961 assert s == 'xyzzy', s
962 s = act.strfunction([t1], [s1], env)
963 assert s == 'xyzzy t1 s1', s
964 s = act.strfunction([t1, t2], [s1, s2], env)
965 assert s == 'xyzzy t1 t2 s1 s2', s
967 act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES',
968 cmdstr='cmdstr = $SOURCES = $TARGETS =')
969 s = act.strfunction([], [], env)
970 assert s == 'cmdstr = = =', s
971 s = act.strfunction([t1], [s1], env)
972 assert s == 'cmdstr = s1 = t1 =', s
973 s = act.strfunction([t1, t2], [s1, s2], env)
974 assert s == 'cmdstr = s1 s2 = t1 t2 =', s
976 act = SCons.Action.CommandAction(['xyzzy',
977 '$TARGET', '$SOURCE',
978 '$TARGETS', '$SOURCES'])
979 s = act.strfunction([], [], env)
980 assert s == 'xyzzy', s
981 s = act.strfunction([t1], [s1], env)
982 assert s == 'xyzzy t1 s1 t1 s1', s
983 s = act.strfunction([t1, t2], [s1, s2], env)
984 assert s == 'xyzzy t1 s1 t1 t2 s1 s2', s
986 act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES',
987 cmdstr='cmdstr\t$TARGETS\n$SOURCES ')
989 s = act.strfunction([], [], env)
990 assert s == 'cmdstr\t\n ', s
991 s = act.strfunction([t1], [s1], env)
992 assert s == 'cmdstr\tt1\ns1 ', s
993 s = act.strfunction([t1, t2], [s1, s2], env)
994 assert s == 'cmdstr\tt1 t2\ns1 s2 ', s
996 def sf(target, source, env) -> str:
997 return "sf was called"
999 act = SCons.Action.CommandAction('foo', strfunction=sf)
1000 s = act.strfunction([], [], env)
1001 assert s == "sf was called", s
1003 class actclass1:
1004 def __init__(self, targets, sources, env) -> None:
1005 pass
1007 def __call__(self) -> int:
1008 return 1
1010 class actclass2:
1011 def __init__(self, targets, sources, env) -> None:
1012 self.strfunction = 5
1014 def __call__(self) -> int:
1015 return 2
1017 class actclass3:
1018 def __init__(self, targets, sources, env) -> None:
1019 pass
1021 def __call__(self) -> int:
1022 return 3
1024 def strfunction(self, targets, sources, env) -> str:
1025 return 'actclass3 on %s to get %s' % (str(sources[0]),
1026 str(targets[0]))
1028 class actclass4:
1029 def __init__(self, targets, sources, env) -> None:
1030 pass
1032 def __call__(self) -> int:
1033 return 4
1035 strfunction = None
1037 act1 = SCons.Action.Action(actclass1([t1], [s1], env))
1038 s = act1.strfunction([t1], [s1], env)
1039 assert s == 'actclass1(["t1"], ["s1"])', s
1041 act2 = SCons.Action.Action(actclass2([t1], [s1], env))
1042 s = act2.strfunction([t1], [s1], env)
1043 assert s == 'actclass2(["t1"], ["s1"])', s
1045 act3 = SCons.Action.Action(actclass3([t1], [s1], env))
1046 s = act3.strfunction([t1], [s1], env)
1047 assert s == 'actclass3 on s1 to get t1', s
1049 act4 = SCons.Action.Action(actclass4([t1], [s1], env))
1050 s = act4.strfunction([t1], [s1], env)
1051 assert s is None, s
1053 act = SCons.Action.CommandAction("@foo bar")
1054 s = act.strfunction([], [], env)
1055 assert s == "", s
1057 act = SCons.Action.CommandAction("@-foo bar")
1058 s = act.strfunction([], [], env)
1059 assert s == "", s
1061 act = SCons.Action.CommandAction("-@foo bar")
1062 s = act.strfunction([], [], env)
1063 assert s == "", s
1065 act = SCons.Action.CommandAction("-foo bar")
1066 s = act.strfunction([], [], env)
1067 assert s == "foo bar", s
1069 act = SCons.Action.CommandAction("@ foo bar")
1070 s = act.strfunction([], [], env)
1071 assert s == "", s
1073 act = SCons.Action.CommandAction("@- foo bar")
1074 s = act.strfunction([], [], env)
1075 assert s == "", s
1077 act = SCons.Action.CommandAction("-@ foo bar")
1078 s = act.strfunction([], [], env)
1079 assert s == "", s
1081 act = SCons.Action.CommandAction("- foo bar")
1082 s = act.strfunction([], [], env)
1083 assert s == "foo bar", s
1085 def test_execute(self) -> None:
1086 """Test execution of command Actions."""
1088 try:
1089 env = self.env
1090 except AttributeError:
1091 env = Environment()
1093 cmd1 = r'%s %s %s xyzzy' % (_python_, act_py, outfile)
1095 act = SCons.Action.CommandAction(cmd1)
1096 r = act([], [], env.Clone())
1097 assert r == 0
1098 c = test.read(outfile, 'r')
1099 assert c == "act.py: 'xyzzy'\n", c
1101 cmd2 = r'%s %s %s $TARGET' % (_python_, act_py, outfile)
1103 act = SCons.Action.CommandAction(cmd2)
1104 r = act(DummyNode('foo'), [], env.Clone())
1105 assert r == 0
1106 c = test.read(outfile, 'r')
1107 assert c == "act.py: 'foo'\n", c
1109 cmd3 = r'%s %s %s ${TARGETS}' % (_python_, act_py, outfile)
1111 act = SCons.Action.CommandAction(cmd3)
1112 r = act(list(map(DummyNode, ['aaa', 'bbb'])), [], env.Clone())
1113 assert r == 0
1114 c = test.read(outfile, 'r')
1115 assert c == "act.py: 'aaa' 'bbb'\n", c
1117 cmd4 = r'%s %s %s $SOURCES' % (_python_, act_py, outfile)
1119 act = SCons.Action.CommandAction(cmd4)
1120 r = act([], [DummyNode('one'), DummyNode('two')], env.Clone())
1121 assert r == 0
1122 c = test.read(outfile, 'r')
1123 assert c == "act.py: 'one' 'two'\n", c
1125 cmd4 = r'%s %s %s ${SOURCES[:2]}' % (_python_, act_py, outfile)
1127 act = SCons.Action.CommandAction(cmd4)
1128 sources = [DummyNode('three'), DummyNode('four'), DummyNode('five')]
1129 env2 = env.Clone()
1130 r = act([], source=sources, env=env2)
1131 assert r == 0
1132 c = test.read(outfile, 'r')
1133 assert c == "act.py: 'three' 'four'\n", c
1135 cmd5 = r'%s %s %s $TARGET XYZZY' % (_python_, act_py, outfile)
1137 act = SCons.Action.CommandAction(cmd5)
1138 env5 = Environment()
1139 if 'ENV' in scons_env:
1140 env5['ENV'] = scons_env['ENV']
1141 PATH = scons_env['ENV'].get('PATH', '')
1142 else:
1143 env5['ENV'] = {}
1144 PATH = ''
1146 env5['ENV']['XYZZY'] = 'xyzzy'
1147 r = act(target=DummyNode('out5'), source=[], env=env5)
1149 act = SCons.Action.CommandAction(cmd5)
1150 r = act(target=DummyNode('out5'),
1151 source=[],
1152 env=env.Clone(ENV={'XYZZY': 'xyzzy5',
1153 'PATH': PATH}))
1154 assert r == 0
1155 c = test.read(outfile, 'r')
1156 assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy5'\n", c
1158 class Obj:
1159 def __init__(self, str) -> None:
1160 self._str = str
1162 def __str__(self) -> str:
1163 return self._str
1165 def rfile(self):
1166 return self
1168 def get_subst_proxy(self):
1169 return self
1171 cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (_python_, act_py, outfile)
1173 act = SCons.Action.CommandAction(cmd6)
1174 r = act(target=[Obj('111'), Obj('222')],
1175 source=[Obj('333'), Obj('444'), Obj('555')],
1176 env=env.Clone())
1177 assert r == 0
1178 c = test.read(outfile, 'r')
1179 assert c == "act.py: '222' '111' '333' '444'\n", c
1181 if os.name == 'nt':
1182 # NT treats execs of directories and non-executable files
1183 # as "file not found" errors
1184 expect_nonexistent = 1
1185 expect_nonexecutable_file = 1
1186 expect_nonexecutable_dir = 1
1187 elif sys.platform == 'cygwin':
1188 expect_nonexistent = 127
1189 # Newer cygwin seems to return 126 for following
1190 expect_nonexecutable_file = 126
1191 expect_nonexecutable_dir = 127
1192 else:
1193 expect_nonexistent = 127
1194 expect_nonexecutable_file = 126
1195 expect_nonexecutable_dir = 126
1197 # Test that a nonexistent command returns 127
1198 act = SCons.Action.CommandAction(python + "_no_such_command_")
1199 r = act([], [], env.Clone(out=outfile))
1200 assert r.status == expect_nonexistent, r.status
1202 # Test that trying to execute a directory returns 126
1203 dir, tail = os.path.split(python)
1204 act = SCons.Action.CommandAction(dir)
1205 r = act([], [], env.Clone(out=outfile))
1206 assert r.status == expect_nonexecutable_file, r.status
1208 # Test that trying to execute a non-executable file returns 126
1209 act = SCons.Action.CommandAction(outfile)
1210 r = act([], [], env.Clone(out=outfile))
1211 assert r.status == expect_nonexecutable_dir, r.status
1213 act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py))
1214 r = act([], [], env)
1215 assert r.status == 1, r.status
1217 act = SCons.Action.CommandAction('@%s %s 1' % (_python_, exit_py))
1218 r = act([], [], env)
1219 assert r.status == 1, r.status
1221 act = SCons.Action.CommandAction('@-%s %s 1' % (_python_, exit_py))
1222 r = act([], [], env)
1223 assert r == 0, r
1225 act = SCons.Action.CommandAction('-%s %s 1' % (_python_, exit_py))
1226 r = act([], [], env)
1227 assert r == 0, r
1229 act = SCons.Action.CommandAction('@ %s %s 1' % (_python_, exit_py))
1230 r = act([], [], env)
1231 assert r.status == 1, r.status
1233 act = SCons.Action.CommandAction('@- %s %s 1' % (_python_, exit_py))
1234 r = act([], [], env)
1235 assert r == 0, r
1237 act = SCons.Action.CommandAction('- %s %s 1' % (_python_, exit_py))
1238 r = act([], [], env)
1239 assert r == 0, r
1241 def test_set_handler(self) -> None:
1242 """Test setting the command handler..."""
1244 class Test:
1245 def __init__(self) -> None:
1246 self.executed = 0
1248 t = Test()
1250 def func(sh, escape, cmd, args, env, test=t) -> int:
1251 test.executed = args
1252 test.shell = sh
1253 return 0
1255 def escape_func(cmd):
1256 return '**' + cmd + '**'
1258 class LiteralStr:
1259 def __init__(self, x) -> None:
1260 self.data = x
1262 def __str__(self) -> str:
1263 return self.data
1265 def escape(self, escape_func):
1266 return escape_func(self.data)
1268 def is_literal(self) -> bool:
1269 return True
1271 a = SCons.Action.CommandAction(["xyzzy"])
1272 e = Environment(SPAWN=func)
1273 a([], [], e)
1274 assert t.executed == ['xyzzy'], t.executed
1276 a = SCons.Action.CommandAction(["xyzzy"])
1277 e = Environment(SPAWN='$FUNC', FUNC=func)
1278 a([], [], e)
1279 assert t.executed == ['xyzzy'], t.executed
1281 a = SCons.Action.CommandAction(["xyzzy"])
1282 e = Environment(SPAWN=func, SHELL='fake shell')
1283 a([], [], e)
1284 assert t.executed == ['xyzzy'], t.executed
1285 assert t.shell == 'fake shell', t.shell
1287 a = SCons.Action.CommandAction([LiteralStr("xyzzy")])
1288 e = Environment(SPAWN=func, ESCAPE=escape_func)
1289 a([], [], e)
1290 assert t.executed == ['**xyzzy**'], t.executed
1292 def test_get_contents(self) -> None:
1293 """Test fetching the contents of a command Action."""
1295 def CmdGen(target, source, env, for_signature) -> str:
1296 assert for_signature
1297 return "%s %s" % \
1298 (env["foo"], env["bar"])
1300 # The number 1 is there to make sure all args get converted to strings.
1301 a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$(", "$bar",
1302 "$)", "stuff", "$)", "|", "$baz", 1])
1303 c = a.get_contents(target=[], source=[],
1304 env=Environment(foo='FFF', bar='BBB',
1305 baz=CmdGen))
1306 assert c == b"| | FFF BBB 1", c
1308 # Make sure that CommandActions use an Environment's
1309 # subst_target_source() method for substitution.
1310 class SpecialEnvironment(Environment):
1311 def subst_target_source(self, strSubst, raw: int=0, target=[], source=[]):
1312 return 'subst_target_source: ' + strSubst
1314 c = a.get_contents(target=DummyNode('ttt'), source=DummyNode('sss'),
1315 env=SpecialEnvironment(foo='GGG', bar='CCC',
1316 baz='ZZZ'))
1317 assert c == b'subst_target_source: | $( $foo | $( $bar $) stuff $) | $baz 1', c
1319 # We've discussed using the real target and source names in a
1320 # CommandAction's signature contents. This would have have the
1321 # advantage of recompiling when a file's name changes (keeping
1322 # debug info current), but it would currently break repository
1323 # logic that will change the file name based on whether the
1324 # files come from a repository or locally. If we ever move to
1325 # that scheme, then all of the '__t1__' and '__s6__' file names
1326 # in the asserts below would change to 't1' and 's6' and the
1327 # like.
1328 t = list(map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6']))
1329 s = list(map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6']))
1330 env = Environment()
1332 a = SCons.Action.CommandAction(["$TARGET"])
1333 c = a.get_contents(target=t, source=s, env=env)
1334 assert c == b"t1", c
1336 a = SCons.Action.CommandAction(["$TARGETS"])
1337 c = a.get_contents(target=t, source=s, env=env)
1338 assert c == b"t1 t2 t3 t4 t5 t6", c
1340 a = SCons.Action.CommandAction(["${TARGETS[2]}"])
1341 c = a.get_contents(target=t, source=s, env=env)
1342 assert c == b"t3", c
1344 a = SCons.Action.CommandAction(["${TARGETS[3:5]}"])
1345 c = a.get_contents(target=t, source=s, env=env)
1346 assert c == b"t4 t5", c
1348 a = SCons.Action.CommandAction(["$SOURCE"])
1349 c = a.get_contents(target=t, source=s, env=env)
1350 assert c == b"s1", c
1352 a = SCons.Action.CommandAction(["$SOURCES"])
1353 c = a.get_contents(target=t, source=s, env=env)
1354 assert c == b"s1 s2 s3 s4 s5 s6", c
1356 a = SCons.Action.CommandAction(["${SOURCES[2]}"])
1357 c = a.get_contents(target=t, source=s, env=env)
1358 assert c == b"s3", c
1360 a = SCons.Action.CommandAction(["${SOURCES[3:5]}"])
1361 c = a.get_contents(target=t, source=s, env=env)
1362 assert c == b"s4 s5", c
1364 def test_get_implicit_deps(self) -> None:
1365 """Test getting the implicit dependencies of a command Action."""
1367 class SpecialEnvironment(Environment):
1368 def WhereIs(self, prog):
1369 return prog
1371 class fs:
1372 def File(name):
1373 return name
1375 env = SpecialEnvironment()
1376 a = SCons.Action.CommandAction([_python_, exit_py])
1377 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [_python_])
1379 for false_like_value in [False, 0, None, "None", "False", "0", "NONE", "FALSE", "off", "OFF", "NO", "no"]:
1380 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES=false_like_value)
1381 a = SCons.Action.CommandAction([_python_, exit_py])
1382 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [])
1384 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES="$SUBST_VALUE", SUBST_VALUE=false_like_value)
1385 a = SCons.Action.CommandAction([_python_, exit_py])
1386 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [])
1388 for true_like_value in [True, 1, "True", "TRUE", "1"]:
1389 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES=true_like_value)
1390 a = SCons.Action.CommandAction([_python_, exit_py])
1391 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [_python_])
1393 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES="$SUBST_VALUE", SUBST_VALUE=true_like_value)
1394 a = SCons.Action.CommandAction([_python_, exit_py])
1395 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [_python_])
1397 for all_like_Value in ["all", "ALL", 2, "2"]:
1398 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES=all_like_Value)
1399 a = SCons.Action.CommandAction([_python_, exit_py])
1400 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [_python_, exit_py])
1403 class CommandGeneratorActionTestCase(unittest.TestCase):
1405 def factory(self, act, **kw):
1406 """Pass any keywords as a dict"""
1408 return SCons.Action.CommandGeneratorAction(act, kw)
1410 def test___init__(self) -> None:
1411 """Test creation of a command generator Action."""
1413 def f(target, source, env) -> None:
1414 pass
1416 a = self.factory(f)
1417 assert a.generator == f
1419 def test___str__(self) -> None:
1420 """Test the pre-substitution strings for command generator Actions."""
1422 def f(target, source, env, for_signature, self=self) -> str:
1423 # See if "env" is really a construction environment (or
1424 # looks like one) by accessing the FindIxes attribute.
1425 # (The Tool/mingw.py module has a generator that uses this,
1426 # and the __str__() method used to cause problems by passing
1427 # us a regular dictionary as a fallback.)
1429 env.FindIxes
1430 return "FOO"
1432 a = self.factory(f)
1433 s = str(a)
1434 assert s == 'FOO', s
1436 def test_genstring(self) -> None:
1437 """Test the command generator Action genstring() method."""
1439 def f(target, source, env, for_signature, self=self) -> str:
1440 dummy = env['dummy']
1441 self.dummy = dummy
1442 return "$FOO $TARGET $SOURCE $TARGETS $SOURCES"
1444 a = self.factory(f)
1445 self.dummy = 0
1446 s = a.genstring([], [], env=Environment(FOO='xyzzy', dummy=1))
1447 assert self.dummy == 1, self.dummy
1448 assert s == "$FOO $TARGET $SOURCE $TARGETS $SOURCES", s
1450 def test_execute(self) -> None:
1451 """Test executing a command generator Action."""
1453 def f(target, source, env, for_signature, self=self) -> str:
1454 dummy = env['dummy']
1455 self.dummy = dummy
1456 s = env.subst("$FOO")
1457 assert s == 'foo baz\nbar ack', s
1458 return "$FOO"
1460 def func_action(target, source, env, self=self) -> None:
1461 dummy = env['dummy']
1462 s = env.subst('$foo')
1463 assert s == 'bar', s
1464 self.dummy = dummy
1466 def f2(target, source, env, for_signature, f=func_action):
1467 return f
1469 def ch(sh, escape, cmd, args, env, self=self) -> None:
1470 self.cmd.append(cmd)
1471 self.args.append(args)
1473 a = self.factory(f)
1474 self.dummy = 0
1475 self.cmd = []
1476 self.args = []
1477 a([], [], env=Environment(FOO='foo baz\nbar ack',
1478 dummy=1,
1479 SPAWN=ch))
1480 assert self.dummy == 1, self.dummy
1481 assert self.cmd == ['foo', 'bar'], self.cmd
1482 assert self.args == [['foo', 'baz'], ['bar', 'ack']], self.args
1484 b = self.factory(f2)
1485 self.dummy = 0
1486 b(target=[], source=[], env=Environment(foo='bar',
1487 dummy=2))
1488 assert self.dummy == 2, self.dummy
1489 del self.dummy
1491 class DummyFile:
1492 def __init__(self, t) -> None:
1493 self.t = t
1495 def rfile(self):
1496 self.t.rfile_called = 1
1497 return self
1499 def get_subst_proxy(self):
1500 return self
1502 def f3(target, source, env, for_signature) -> str:
1503 return ''
1505 c = self.factory(f3)
1506 c(target=[], source=DummyFile(self), env=Environment())
1507 assert self.rfile_called
1509 def test_get_contents(self) -> None:
1510 """Test fetching the contents of a command generator Action."""
1512 def f(target, source, env, for_signature):
1513 foo = env['foo']
1514 bar = env['bar']
1515 assert for_signature, for_signature
1516 return [["guux", foo, "$(", "$ignore", "$)", bar,
1517 '${test("$( foo $bar $)")}']]
1519 def test(mystr) -> str:
1520 assert mystr == "$( foo $bar $)", mystr
1521 return "test"
1523 env = Environment(foo='FFF', bar='BBB',
1524 ignore='foo', test=test)
1525 a = self.factory(f)
1526 c = a.get_contents(target=[], source=[], env=env)
1527 assert c == b"guux FFF BBB test", c
1529 def test_get_contents_of_function_action(self) -> None:
1530 """Test contents of a CommandGeneratorAction-generated FunctionAction."""
1532 def LocalFunc() -> None:
1533 pass
1535 # Since the python bytecode has per version differences, we need different expected results per version
1536 func_matches = {
1537 (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'),
1538 (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1539 (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1540 (3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1541 (3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1542 (3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1543 (3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
1544 (3, 12): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00y\x00),(),()'),
1547 meth_matches = [
1548 b"1, 1, 0, 0,(),(),(d\000\000S),(),()",
1549 b"1, 1, 0, 0,(),(),(d\x00\x00S),(),()",
1552 def f_global(target, source, env, for_signature):
1553 return SCons.Action.Action(GlobalFunc)
1555 def f_local(target, source, env, for_signature):
1556 return SCons.Action.Action(LocalFunc)
1558 env = Environment(XYZ='foo')
1560 with self.subTest():
1561 a = self.factory(f_global)
1562 c = a.get_contents(target=[], source=[], env=env)
1563 self.assertEqual(c, func_matches[sys.version_info[:2]])
1565 with self.subTest():
1566 a = self.factory(f_local)
1567 c = a.get_contents(target=[], source=[], env=env)
1568 self.assertEqual(c, func_matches[sys.version_info[:2]])
1570 def f_global2(target, source, env, for_signature):
1571 return SCons.Action.Action(GlobalFunc, varlist=['XYZ'])
1573 def f_local2(target, source, env, for_signature):
1574 return SCons.Action.Action(LocalFunc, varlist=['XYZ'])
1576 matches_foo = func_matches[sys.version_info[:2]] + b'foo'
1578 with self.subTest():
1579 a = self.factory(f_global2)
1580 c = a.get_contents(target=[], source=[], env=env)
1581 self.assertIn(c, matches_foo)
1583 with self.subTest():
1584 a = self.factory(f_local2)
1585 c = a.get_contents(target=[], source=[], env=env)
1586 self.assertIn(c, matches_foo)
1589 class FunctionActionTestCase(unittest.TestCase):
1591 def test___init__(self) -> None:
1592 """Test creation of a function Action."""
1594 def func1() -> None:
1595 pass
1597 def func2() -> None:
1598 pass
1600 def func3() -> None:
1601 pass
1603 def func4() -> None:
1604 pass
1606 a = SCons.Action.FunctionAction(func1, {})
1607 assert a.execfunction == func1, a.execfunction
1608 assert isinstance(a.strfunction, types.MethodType), type(a.strfunction)
1610 a = SCons.Action.FunctionAction(func2, {'strfunction': func3})
1611 assert a.execfunction == func2, a.execfunction
1612 assert a.strfunction == func3, a.strfunction
1614 def test___str__(self) -> None:
1615 """Test the __str__() method for function Actions."""
1617 def func1() -> None:
1618 pass
1620 a = SCons.Action.FunctionAction(func1, {})
1621 s = str(a)
1622 assert s == "func1(target, source, env)", s
1624 class class1:
1625 def __call__(self) -> None:
1626 pass
1628 a = SCons.Action.FunctionAction(class1(), {})
1629 s = str(a)
1630 assert s == "class1(target, source, env)", s
1632 def test_execute(self) -> None:
1633 """Test executing a function Action."""
1635 self.inc = 0
1637 def f(target, source, env) -> int:
1638 s = env['s']
1639 s.inc = s.inc + 1
1640 s.target = target
1641 s.source = source
1642 assert env.subst("$BAR") == 'foo bar', env.subst("$BAR")
1643 return 0
1645 a = SCons.Action.FunctionAction(f, {})
1646 a(target=1, source=2, env=Environment(BAR='foo bar',
1647 s=self))
1648 assert self.inc == 1, self.inc
1649 assert self.source == [2], self.source
1650 assert self.target == [1], self.target
1652 global count
1653 count = 0
1655 def function1(target, source, env) -> int:
1656 global count
1657 count = count + 1
1658 for t in target:
1659 with open(t, 'w') as f:
1660 f.write("function1\n")
1661 return 1
1663 act = SCons.Action.FunctionAction(function1, {})
1664 r = act(target=[outfile, outfile2], source=[], env=Environment())
1665 assert r.status == 1, r.status
1667 assert count == 1, count
1668 c = test.read(outfile, 'r')
1669 assert c == "function1\n", c
1670 c = test.read(outfile2, 'r')
1671 assert c == "function1\n", c
1673 class class1a:
1674 def __init__(self, target, source, env) -> None:
1675 with open(env['out'], 'w') as f:
1676 f.write("class1a\n")
1678 act = SCons.Action.FunctionAction(class1a, {})
1679 r = act([], [], Environment(out=outfile))
1680 assert isinstance(r.status, class1a), r.status
1681 c = test.read(outfile, 'r')
1682 assert c == "class1a\n", c
1684 class class1b:
1685 def __call__(self, target, source, env) -> int:
1686 with open(env['out'], 'w') as f:
1687 f.write("class1b\n")
1688 return 2
1690 act = SCons.Action.FunctionAction(class1b(), {})
1691 r = act([], [], Environment(out=outfile))
1692 assert r.status == 2, r.status
1693 c = test.read(outfile, 'r')
1694 assert c == "class1b\n", c
1696 def build_it(target, source, env, executor=None, self=self) -> int:
1697 self.build_it = 1
1698 return 0
1700 def string_it(target, source, env, executor=None, self=self):
1701 self.string_it = 1
1702 return None
1704 act = SCons.Action.FunctionAction(build_it,
1705 {'strfunction': string_it})
1706 r = act([], [], Environment())
1707 assert r == 0, r
1708 assert self.build_it
1709 assert self.string_it
1711 def test_get_contents(self) -> None:
1712 """Test fetching the contents of a function Action."""
1714 def LocalFunc() -> None:
1715 pass
1717 func_matches = {
1718 (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'),
1719 (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1720 (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1721 (3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1722 (3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1723 (3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1724 (3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
1725 (3, 12): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00y\x00),(),()'),
1729 meth_matches = {
1730 (3, 5): bytearray(b'1, 1, 0, 0,(),(),(d\x00\x00S),(),()'),
1731 (3, 6): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1732 (3, 7): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1733 (3, 8): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1734 (3, 9): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1735 (3, 10): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1736 (3, 11): bytearray(b'1, 1, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
1737 (3, 12): bytearray(b'1, 1, 0, 0,(),(),(\x97\x00y\x00),(),()'),
1740 def factory(act, **kw):
1741 return SCons.Action.FunctionAction(act, kw)
1743 with self.subTest():
1744 a = factory(GlobalFunc)
1745 c = a.get_contents(target=[], source=[], env=Environment())
1746 self.assertEqual(c, func_matches[sys.version_info[:2]])
1748 with self.subTest():
1749 a = factory(LocalFunc)
1750 c = a.get_contents(target=[], source=[], env=Environment())
1751 self.assertEqual(c, func_matches[sys.version_info[:2]])
1753 matches_foo = func_matches[sys.version_info[:2]] + b'foo'
1755 with self.subTest():
1756 a = factory(GlobalFunc, varlist=['XYZ'])
1757 c = a.get_contents(target=[], source=[], env=Environment())
1758 self.assertEqual(c, func_matches[sys.version_info[:2]])
1760 with self.subTest():
1761 c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo'))
1762 self.assertEqual(c, matches_foo)
1764 ##TODO: is this set of tests still needed?
1765 # Make sure a bare string varlist works
1766 with self.subTest():
1767 a = factory(GlobalFunc, varlist='XYZ')
1768 c = a.get_contents(target=[], source=[], env=Environment())
1769 self.assertEqual(c, func_matches[sys.version_info[:2]])
1771 with self.subTest():
1772 c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo'))
1773 self.assertIn(c, matches_foo)
1775 class Foo:
1776 def get_contents(self, target, source, env) -> bytes:
1777 return b'xyzzy'
1779 with self.subTest():
1780 a = factory(Foo())
1781 c = a.get_contents(target=[], source=[], env=Environment())
1782 self.assertEqual(c, b'xyzzy')
1784 class LocalClass:
1785 def LocalMethod(self) -> None:
1786 pass
1788 with self.subTest():
1789 lc = LocalClass()
1790 a = factory(lc.LocalMethod)
1791 c = a.get_contents(target=[], source=[], env=Environment())
1792 self.assertEqual(c, meth_matches[sys.version_info[:2]])
1794 def test_strfunction(self) -> None:
1795 """Test the FunctionAction.strfunction() method."""
1797 def func() -> None:
1798 pass
1800 def factory(act, **kw):
1801 return SCons.Action.FunctionAction(act, kw)
1803 a = factory(func)
1804 s = a.strfunction(target=[], source=[], env=Environment())
1805 assert s == 'func([], [])', s
1807 a = factory(func, strfunction=None)
1808 s = a.strfunction(target=[], source=[], env=Environment())
1809 assert s is None, s
1811 a = factory(func, cmdstr='function')
1812 s = a.strfunction(target=[], source=[], env=Environment())
1813 assert s == 'function', s
1816 class ListActionTestCase(unittest.TestCase):
1818 def test___init__(self) -> None:
1819 """Test creation of a list of subsidiary Actions."""
1821 def func() -> None:
1822 pass
1824 a = SCons.Action.ListAction(["x", func, ["y", "z"]])
1825 assert isinstance(a.list[0], SCons.Action.CommandAction)
1826 assert isinstance(a.list[1], SCons.Action.FunctionAction)
1827 assert isinstance(a.list[2], SCons.Action.ListAction)
1828 assert a.list[2].list[0].cmd_list == 'y'
1830 def test___str__(self) -> None:
1831 """Test the __str__() method for a list of subsidiary Actions."""
1833 def f(target, source, env) -> None:
1834 pass
1836 def g(target, source, env) -> None:
1837 pass
1839 a = SCons.Action.ListAction([f, g, "XXX", f])
1840 s = str(a)
1841 assert s == "f(target, source, env)\ng(target, source, env)\nXXX\nf(target, source, env)", s
1843 def test_genstring(self) -> None:
1844 """Test the genstring() method for a list of subsidiary Actions."""
1846 def f(target, source, env) -> None:
1847 pass
1849 def g(target, source, env, for_signature) -> str:
1850 return 'generated %s %s' % (target[0], source[0])
1852 g = SCons.Action.Action(g, generator=1)
1853 a = SCons.Action.ListAction([f, g, "XXX", f])
1854 s = a.genstring(['foo.x'], ['bar.y'], Environment())
1855 assert s == "f(target, source, env)\ngenerated foo.x bar.y\nXXX\nf(target, source, env)", s
1857 def test_execute(self) -> None:
1858 """Test executing a list of subsidiary Actions."""
1859 self.inc = 0
1861 def f(target, source, env) -> None:
1862 s = env['s']
1863 s.inc = s.inc + 1
1865 a = SCons.Action.ListAction([f, f, f])
1866 a([], [], Environment(s=self))
1867 assert self.inc == 3, self.inc
1869 cmd2 = r'%s %s %s syzygy' % (_python_, act_py, outfile)
1871 def function2(target, source, env) -> int:
1872 with open(env['out'], 'a') as f:
1873 f.write("function2\n")
1874 return 0
1876 class class2a:
1877 def __call__(self, target, source, env) -> int:
1878 with open(env['out'], 'a') as f:
1879 f.write("class2a\n")
1880 return 0
1882 class class2b:
1883 def __init__(self, target, source, env) -> None:
1884 with open(env['out'], 'a') as f:
1885 f.write("class2b\n")
1887 act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b])
1888 r = act([], [], Environment(out=outfile))
1889 assert isinstance(r.status, class2b), r.status
1890 c = test.read(outfile, 'r')
1891 assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
1893 def test_get_contents(self) -> None:
1894 """Test fetching the contents of a list of subsidiary Actions."""
1896 self.foo = 0
1898 def gen(target, source, env, for_signature) -> str:
1899 s = env['s']
1900 s.foo = 1
1901 return "y"
1903 a = SCons.Action.ListAction(["x",
1904 SCons.Action.Action(gen, generator=1),
1905 "z"])
1906 c = a.get_contents(target=[], source=[], env=Environment(s=self))
1907 assert self.foo == 1, self.foo
1908 assert c == b"xyz", c
1911 class LazyActionTestCase(unittest.TestCase):
1912 def test___init__(self) -> None:
1913 """Test creation of a lazy-evaluation Action."""
1915 # Environment variable references should create a special type
1916 # of LazyAction that lazily evaluates the variable for whether
1917 # it's a string or something else before doing anything.
1918 a9 = SCons.Action.Action('$FOO')
1919 assert isinstance(a9, SCons.Action.LazyAction), a9
1920 assert a9.var == 'FOO', a9.var
1922 a10 = SCons.Action.Action('${FOO}')
1923 assert isinstance(a10, SCons.Action.LazyAction), a10
1924 assert a10.var == 'FOO', a10.var
1926 def test_genstring(self) -> None:
1927 """Test the lazy-evaluation Action genstring() method."""
1929 def f(target, source, env) -> None:
1930 pass
1932 a = SCons.Action.Action('$BAR')
1933 env1 = Environment(BAR=f, s=self)
1934 env2 = Environment(BAR='xxx', s=self)
1935 s = a.genstring([], [], env=env1)
1936 assert s == "f(target, source, env)", s
1937 s = a.genstring([], [], env=env2)
1938 assert s == 'xxx', s
1940 def test_execute(self) -> None:
1941 """Test executing a lazy-evaluation Action."""
1943 def f(target, source, env) -> int:
1944 s = env['s']
1945 s.test = 1
1946 return 0
1948 a = SCons.Action.Action('$BAR')
1949 a([], [], env=Environment(BAR=f, s=self))
1950 assert self.test == 1, self.test
1951 cmd = r'%s %s %s lazy' % (_python_, act_py, outfile)
1952 a([], [], env=Environment(BAR=cmd, s=self))
1953 c = test.read(outfile, 'r')
1954 assert c == "act.py: 'lazy'\n", c
1956 def test_get_contents(self) -> None:
1957 """Test fetching the contents of a lazy-evaluation Action."""
1959 a = SCons.Action.Action("${FOO}")
1960 env = Environment(FOO=[["This", "is", "a", "test"]])
1961 c = a.get_contents(target=[], source=[], env=env)
1962 assert c == b"This is a test", c
1964 def test_get_contents_of_function_action(self) -> None:
1965 """Test fetching the contents of a lazy-evaluation FunctionAction."""
1967 def LocalFunc() -> None:
1968 pass
1970 func_matches = {
1971 (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'),
1972 (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1973 (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1974 (3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1975 (3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1976 (3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1977 (3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
1978 (3, 12): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00y\x00),(),()'),
1981 meth_matches = [
1982 b"1, 1, 0, 0,(),(),(d\000\000S),(),()",
1983 b"1, 1, 0, 0,(),(),(d\x00\x00S),(),()",
1986 def factory(act, **kw):
1987 return SCons.Action.FunctionAction(act, kw)
1989 a = SCons.Action.Action("${FOO}")
1991 with self.subTest():
1992 env = Environment(FOO=factory(GlobalFunc))
1993 c = a.get_contents(target=[], source=[], env=env)
1994 self.assertEqual(c, func_matches[sys.version_info[:2]])
1996 with self.subTest():
1997 env = Environment(FOO=factory(LocalFunc))
1998 c = a.get_contents(target=[], source=[], env=env)
1999 self.assertEqual(c, func_matches[sys.version_info[:2]])
2001 # matches_foo = [x + b"foo" for x in func_matches]
2002 matches_foo = func_matches[sys.version_info[:2]] + b'foo'
2004 with self.subTest():
2005 env = Environment(FOO=factory(GlobalFunc, varlist=['XYZ']))
2006 c = a.get_contents(target=[], source=[], env=env)
2007 self.assertEqual(c, func_matches[sys.version_info[:2]])
2009 with self.subTest():
2010 env['XYZ'] = 'foo'
2011 c = a.get_contents(target=[], source=[], env=env)
2012 self.assertIn(c, matches_foo)
2015 class ActionCallerTestCase(unittest.TestCase):
2016 def test___init__(self) -> None:
2017 """Test creation of an ActionCaller"""
2019 ac = SCons.Action.ActionCaller(1, [2, 3], {'FOO': 4, 'BAR': 5})
2020 assert ac.parent == 1, ac.parent
2021 assert ac.args == [2, 3], ac.args
2022 assert ac.kw == {'FOO': 4, 'BAR': 5}, ac.kw
2024 def test_get_contents(self) -> None:
2025 """Test fetching the contents of an ActionCaller"""
2027 def strfunc() -> None:
2028 pass
2030 def LocalFunc() -> None:
2031 pass
2033 matches = {
2034 (3, 5): b'd\x00\x00S',
2035 (3, 6): b'd\x00S\x00',
2036 (3, 7): b'd\x00S\x00',
2037 (3, 8): b'd\x00S\x00',
2038 (3, 9): b'd\x00S\x00',
2039 (3, 10): b'd\x00S\x00',
2040 (3, 11): b'\x97\x00d\x00S\x00',
2041 (3, 12): b'\x97\x00y\x00',
2044 with self.subTest():
2045 af = SCons.Action.ActionFactory(GlobalFunc, strfunc)
2046 ac = SCons.Action.ActionCaller(af, [], {})
2047 c = ac.get_contents([], [], Environment())
2048 self.assertEqual(c, matches[sys.version_info[:2]])
2050 with self.subTest():
2051 af = SCons.Action.ActionFactory(LocalFunc, strfunc)
2052 ac = SCons.Action.ActionCaller(af, [], {})
2053 c = ac.get_contents([], [], Environment())
2054 self.assertEqual(c, matches[sys.version_info[:2]])
2056 class LocalActFunc:
2057 def __call__(self) -> None:
2058 pass
2060 with self.subTest():
2061 af = SCons.Action.ActionFactory(GlobalActFunc(), strfunc)
2062 ac = SCons.Action.ActionCaller(af, [], {})
2063 c = ac.get_contents([], [], Environment())
2064 self.assertEqual(c, matches[sys.version_info[:2]])
2066 with self.subTest():
2067 af = SCons.Action.ActionFactory(LocalActFunc(), strfunc)
2068 ac = SCons.Action.ActionCaller(af, [], {})
2069 c = ac.get_contents([], [], Environment())
2070 self.assertEqual(c, matches[sys.version_info[:2]])
2072 matches = [
2073 b"<built-in function str>",
2074 b"<type 'str'>",
2077 with self.subTest():
2078 af = SCons.Action.ActionFactory(str, strfunc)
2079 ac = SCons.Action.ActionCaller(af, [], {})
2080 c = ac.get_contents([], [], Environment())
2081 self.assertIn(c, ("<built-in function str>", "<type 'str'>", "<class 'str'>"))
2082 # ^^ class str for python3
2084 def test___call__(self) -> None:
2085 """Test calling an ActionCaller"""
2087 actfunc_args = []
2089 def actfunc(a1, a2, a3, args=actfunc_args) -> None:
2090 args.extend([a1, a2, a3])
2092 def strfunc(a1, a2, a3) -> None:
2093 pass
2095 e = Environment(FOO=2, BAR=5)
2097 af = SCons.Action.ActionFactory(actfunc, strfunc)
2098 ac = SCons.Action.ActionCaller(af, ['$__env__', '$FOO', 3], {})
2099 ac([], [], e)
2100 assert actfunc_args[0] is e, actfunc_args
2101 assert actfunc_args[1] == '2', actfunc_args
2102 assert actfunc_args[2] == 3, actfunc_args
2103 del actfunc_args[:]
2105 ac = SCons.Action.ActionCaller(af, [], {'a3': '$__env__', 'a2': '$BAR', 'a1': 4})
2106 ac([], [], e)
2107 assert actfunc_args[0] == 4, actfunc_args
2108 assert actfunc_args[1] == '5', actfunc_args
2109 assert actfunc_args[2] is e, actfunc_args
2110 del actfunc_args[:]
2112 def test_strfunction(self) -> None:
2113 """Test calling the ActionCaller strfunction() method"""
2115 strfunc_args = []
2117 def actfunc(a1, a2, a3, a4) -> None:
2118 pass
2120 def strfunc(a1, a2, a3, a4, args=strfunc_args) -> None:
2121 args.extend([a1, a2, a3, a4])
2123 af = SCons.Action.ActionFactory(actfunc, strfunc)
2124 ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3, '$WS'], {})
2125 ac.strfunction([], [], Environment(FOO=2, WS='white space'))
2126 assert strfunc_args == [1, '2', 3, 'white space'], strfunc_args
2128 del strfunc_args[:]
2129 d = {'a3': 6, 'a2': '$BAR', 'a1': 4, 'a4': '$WS'}
2130 ac = SCons.Action.ActionCaller(af, [], d)
2131 ac.strfunction([], [], Environment(BAR=5, WS='w s'))
2132 assert strfunc_args == [4, '5', 6, 'w s'], strfunc_args
2135 class ActionFactoryTestCase(unittest.TestCase):
2136 def test___init__(self) -> None:
2137 """Test creation of an ActionFactory"""
2139 def actfunc() -> None:
2140 pass
2142 def strfunc() -> None:
2143 pass
2145 ac = SCons.Action.ActionFactory(actfunc, strfunc)
2146 assert ac.actfunc is actfunc, ac.actfunc
2147 assert ac.strfunc is strfunc, ac.strfunc
2149 def test___call__(self) -> None:
2150 """Test calling whatever's returned from an ActionFactory"""
2152 actfunc_args = []
2153 strfunc_args = []
2155 def actfunc(a1, a2, a3, args=actfunc_args) -> None:
2156 args.extend([a1, a2, a3])
2158 def strfunc(a1, a2, a3, args=strfunc_args) -> None:
2159 args.extend([a1, a2, a3])
2161 af = SCons.Action.ActionFactory(actfunc, strfunc)
2162 af(3, 6, 9)([], [], Environment())
2163 assert actfunc_args == [3, 6, 9], actfunc_args
2164 assert strfunc_args == [3, 6, 9], strfunc_args
2167 class ActionCompareTestCase(unittest.TestCase):
2168 def test_1_solo_name(self) -> None:
2169 """Test Lazy Cmd Generator Action get_name alone.
2171 Basically ensures we can locate the builder, comparing it to
2172 itself along the way."""
2174 bar = SCons.Builder.Builder(action={})
2175 env = Environment(BUILDERS={'BAR': bar})
2176 name = bar.get_name(env)
2177 assert name == 'BAR', name
2179 def test_2_multi_name(self) -> None:
2180 """Test LazyCmdGenerator Action get_name multi builders.
2182 Ensure that we can compare builders (and thereby actions) to
2183 each other safely."""
2185 foo = SCons.Builder.Builder(action='$FOO', suffix='.foo')
2186 bar = SCons.Builder.Builder(action={})
2187 assert foo != bar
2188 assert foo.action != bar.action
2189 env = Environment(BUILDERS={'FOO': foo, 'BAR': bar})
2190 name = foo.get_name(env)
2191 assert name == 'FOO', name
2192 name = bar.get_name(env)
2193 assert name == 'BAR', name
2195 def test_3_dict_names(self) -> None:
2196 """Test Action/Suffix dicts with get_name.
2198 Verifies that Action/Suffix dictionaries work correctly,
2199 especially two builders that can generate the same suffix,
2200 where one of the builders has a suffix dictionary with a None
2201 key."""
2203 foo = SCons.Builder.Builder(action='$FOO', suffix='.foo')
2204 bar = SCons.Builder.Builder(action={}, suffix={None: '.bar'})
2205 bar.add_action('.cow', "$MOO")
2206 dog = SCons.Builder.Builder(suffix='.bar')
2207 env = Environment(BUILDERS={'FOO': foo, 'BAR': bar, 'DOG': dog})
2209 assert foo.get_name(env) == 'FOO', foo.get_name(env)
2210 assert bar.get_name(env) == 'BAR', bar.get_name(env)
2211 assert dog.get_name(env) == 'DOG', dog.get_name(env)
2214 class TestClass:
2215 """A test class used by ObjectContentsTestCase.test_object_contents"""
2217 def __init__(self) -> None:
2218 self.a = "a"
2219 self.b = "b"
2221 def method(self, arg) -> None:
2222 pass
2225 class ObjectContentsTestCase(unittest.TestCase):
2226 def test_function_contents(self) -> None:
2227 """Test that Action._function_contents works"""
2229 def func1(a, b, c):
2230 """A test function"""
2231 return a
2233 # Since the python bytecode has per version differences,
2234 # we need different expected results per version
2235 # Note unlike the others, this result is a tuple, use assertIn
2236 expected = {
2237 (3, 5): (bytearray(b'3, 3, 0, 0,(),(),(|\x00\x00S),(),()'),),
2238 (3, 6): (bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),),
2239 (3, 7): (bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),),
2240 (3, 8): (bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),),
2241 (3, 9): (bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),),
2242 (3, 10): ( # 3.10.1, 3.10.2
2243 bytearray(b'3, 3, 0, 0,(N.),(),(|\x00S\x00),(),()'),
2244 bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),
2246 (3, 11): (bytearray(b'3, 3, 0, 0,(),(),(\x97\x00|\x00S\x00),(),()'),),
2247 (3, 12): (bytearray(b'3, 3, 0, 0,(),(),(\x97\x00|\x00S\x00),(),()'),),
2250 c = SCons.Action._function_contents(func1)
2251 self.assertIn(c, expected[sys.version_info[:2]])
2253 def test_object_contents(self) -> None:
2254 """Test that Action._object_contents works"""
2256 # See definition above
2257 o = TestClass()
2258 c = SCons.Action._object_contents(o)
2260 # c = SCons.Action._object_instance_content(o)
2262 # Since the python bytecode has per version differences,
2263 # we need different expected results per version
2264 expected = {
2265 (3, 5): bytearray(
2266 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01\x00|\x00\x00_\x00\x00d\x02\x00|\x00\x00_\x01\x00d\x00\x00S),(),(),2, 2, 0, 0,(),(),(d\x00\x00S),(),()}}{{{a=a,b=b}}}"
2268 (3, 6): bytearray(
2269 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"
2271 (3, 7): bytearray(
2272 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"
2274 (3, 8): bytearray(
2275 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"
2277 (3, 9): bytearray(
2278 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"
2280 (3, 10): bytearray(
2281 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"
2283 (3, 11): bytearray(
2284 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(\x97\x00d\x01|\x00_\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x02|\x00_\x01\x00\x00\x00\x00\x00\x00\x00\x00d\x00S\x00),(),(),2, 2, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()}}{{{a=a,b=b}}}"
2286 (3, 12): bytearray(
2287 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(\x97\x00d\x01|\x00_\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x02|\x00_\x01\x00\x00\x00\x00\x00\x00\x00\x00y\x00),(),(),2, 2, 0, 0,(),(),(\x97\x00y\x00),(),()}}{{{a=a,b=b}}}"
2291 self.assertEqual(c, expected[sys.version_info[:2]])
2293 def test_code_contents(self) -> None:
2294 """Test that Action._code_contents works"""
2296 code = compile("print('Hello, World!')", '<string>', 'exec')
2297 c = SCons.Action._code_contents(code)
2299 # Since the python bytecode has per version differences, we need different expected results per version
2300 expected = {
2301 (3, 5): bytearray(
2302 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00\x00d\x00\x00\x83\x01\x00\x01d\x01\x00S)'
2304 (3, 6): bytearray(
2305 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2307 (3, 7): bytearray(
2308 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2310 (3, 8): bytearray(
2311 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2313 (3, 9): bytearray(
2314 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2316 (3, 10): bytearray(
2317 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2319 (3, 11): bytearray(
2320 b'0, 0, 0, 0,(Hello, World!),(print),(\x97\x00\x02\x00e\x00d\x00\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x01S\x00)'
2322 (3, 12): bytearray(
2323 b'0, 0, 0, 0,(Hello, World!),(print),(\x97\x00\x02\x00e\x00d\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00y\x01)'
2327 self.assertEqual(c, expected[sys.version_info[:2]])
2329 def test_uncaught_exception_bubbles(self):
2330 """Test that _subproc bubbles uncaught exceptions"""
2332 try:
2333 pobj = SCons.Action._subproc(
2334 Environment(),
2335 None,
2336 stdin='devnull',
2337 stderr='devnull',
2338 stdout=subprocess.PIPE,
2340 pobj.wait()
2341 except EnvironmentError:
2342 pass
2343 except Exception:
2344 # pass the test
2345 return
2347 raise Exception("expected a non-EnvironmentError exception")
2349 if __name__ == "__main__":
2350 unittest.main()
2352 # Local Variables:
2353 # tab-width:4
2354 # indent-tabs-mode:nil
2355 # End:
2356 # vim: set expandtab tabstop=4 shiftwidth=4: