Remove redundant code
[scons.git] / SCons / ActionTests.py
blob39798809e9bdda660bde67df1a251ec6933e1e80
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 from unittest import mock
45 from subprocess import PIPE
46 from typing import Optional
48 import SCons.Action
49 import SCons.Environment
50 import SCons.Errors
51 from SCons.Action import scons_subproc_run
52 from SCons.Util.sctyping import ExecutorType
54 import TestCmd
56 # Initial setup of the common environment for all tests,
57 # a temporary working directory containing a
58 # script for writing arguments to an output file.
60 # We don't do this as a setUp() method because it's
61 # unnecessary to create a separate directory and script
62 # for each test, they can just use the one.
63 test = TestCmd.TestCmd(workdir='')
65 test.write('act.py', """\
66 import os, string, sys
68 with open(sys.argv[1], 'w') as f:
69 f.write("act.py: '" + "' '".join(sys.argv[2:]) + "'\\n")
70 try:
71 if sys.argv[3]:
72 f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n")
73 except:
74 pass
76 if 'ACTPY_PIPE' in os.environ:
77 if 'PIPE_STDOUT_FILE' in os.environ:
78 with open(os.environ['PIPE_STDOUT_FILE'], 'r') as f:
79 stdout_msg = f.read()
80 else:
81 stdout_msg = "act.py: stdout: executed act.py %s\\n" % ' '.join(sys.argv[1:])
82 sys.stdout.write(stdout_msg)
83 if 'PIPE_STDERR_FILE' in os.environ:
84 with open(os.environ['PIPE_STDERR_FILE'], 'r') as f:
85 stderr_msg = f.read()
86 else:
87 stderr_msg = "act.py: stderr: executed act.py %s\\n" % ' '.join(sys.argv[1:])
88 sys.stderr.write(stderr_msg)
89 sys.exit(0)
90 """)
92 test.write('exit.py', """\
93 import sys
94 sys.exit(int(sys.argv[1]))
95 """)
97 act_py = test.workpath('act.py')
98 exit_py = test.workpath('exit.py')
100 outfile = test.workpath('outfile')
101 outfile2 = test.workpath('outfile2')
102 pipe_file = test.workpath('pipe.out')
104 scons_env = SCons.Environment.Environment()
106 # Capture all the stuff the Actions will print,
107 # so it doesn't clutter the output.
108 sys.stdout = io.StringIO()
111 class CmdStringHolder:
112 def __init__(self, cmd, literal=None) -> None:
113 self.data = str(cmd)
114 self.literal = literal
116 def is_literal(self) -> bool:
117 return self.literal
119 def escape(self, escape_func):
120 """Escape the string with the supplied function.
122 The function is expected to take an arbitrary string, then
123 return it with all special characters escaped and ready
124 for passing to the command interpreter.
126 After calling this function, the next call to str() will
127 return the escaped string.
130 if self.is_literal():
131 return escape_func(self.data)
132 elif ' ' in self.data or '\t' in self.data:
133 return '"%s"' % self.data
134 else:
135 return self.data
138 class Environment:
139 def __init__(self, **kw) -> None:
140 self.d = {}
141 self.d['SHELL'] = scons_env['SHELL']
142 self.d['SPAWN'] = scons_env['SPAWN']
143 self.d['PSPAWN'] = scons_env['PSPAWN']
144 self.d['ESCAPE'] = scons_env['ESCAPE']
145 for k, v in kw.items():
146 self.d[k] = v
148 # Just use the underlying scons_subst*() utility methods.
149 def subst(self, strSubst, raw: int=0, target=[], source=[], conv=None, overrides: bool=False):
150 return SCons.Subst.scons_subst(strSubst, self, raw,
151 target, source, self.d, conv=conv, overrides=overrides)
153 subst_target_source = subst
155 def subst_list(self, strSubst, raw: int=0, target=[], source=[], conv=None, overrides: bool=False):
156 return SCons.Subst.scons_subst_list(strSubst, self, raw,
157 target, source, self.d, conv=conv, overrides=overrides)
159 def __getitem__(self, item):
160 return self.d[item]
162 def __setitem__(self, item, value) -> None:
163 self.d[item] = value
165 def __contains__(self, key) -> bool:
166 return key in self.d
168 def get(self, key, value=None):
169 return self.d.get(key, value)
171 def items(self):
172 return list(self.d.items())
174 def Dictionary(self):
175 return self.d
177 def Clone(self, **kw):
178 res = Environment()
179 res.d = SCons.Util.semi_deepcopy(self.d)
180 for k, v in kw.items():
181 res.d[k] = v
182 return res
184 def sig_dict(self):
185 d = {}
186 for k, v in self.items(): d[k] = v
187 d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
188 d['TARGET'] = d['TARGETS'][0]
189 d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
190 d['SOURCE'] = d['SOURCES'][0]
191 return d
194 class DummyNode:
195 def __init__(self, name) -> None:
196 self.name = name
198 def str_for_display(self):
199 return '"' + self.name + '"'
201 def __str__(self) -> str:
202 return self.name
204 def rfile(self):
205 return self
207 def get_subst_proxy(self):
208 return self
211 if os.name == 'java':
212 python = os.path.join(sys.prefix, 'jython')
213 else:
214 python = os.environ.get('python_executable', sys.executable)
215 _python_ = test.escape(python)
217 _null = SCons.Action._null
220 def test_varlist(pos_call, str_call, cmd, cmdstrfunc, **kw) -> None:
221 def call_action(a, pos_call=pos_call, str_call=str_call, kw=kw):
222 a = SCons.Action.Action(*a, **kw)
223 # returned object must provide these entry points
224 assert hasattr(a, '__call__')
225 assert hasattr(a, 'get_contents')
226 assert hasattr(a, 'genstring')
227 pos_call(a)
228 str_call(a)
229 return a
231 a = call_action((cmd, cmdstrfunc))
232 assert a.varlist == (), a.varlist
234 a = call_action((cmd, cmdstrfunc, 'foo'))
235 assert a.varlist == ('foo',), a.varlist
237 a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c'))
238 assert a.varlist == ('a', 'b', 'c'), a.varlist
240 a = call_action((cmd, cmdstrfunc, ['a', 'b', 'c']))
241 assert a.varlist == ('a', 'b', 'c'), a.varlist
243 kw['varlist'] = 'foo'
244 a = call_action((cmd, cmdstrfunc))
245 assert a.varlist == ('foo',), a.varlist
247 kw['varlist'] = ['x', 'y', 'z']
248 a = call_action((cmd, cmdstrfunc))
249 assert a.varlist == ('x', 'y', 'z'), a.varlist
251 a = call_action((cmd, cmdstrfunc, 'foo'))
252 assert a.varlist == ('foo', 'x', 'y', 'z'), a.varlist
254 a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c'))
255 assert a.varlist == ('a', 'b', 'c', 'x', 'y', 'z'), a.varlist
258 def test_positional_args(pos_callback, cmd, **kw):
259 """Test that Action returns the expected type and positional args work."""
261 act = SCons.Action.Action(cmd, **kw)
262 pos_callback(act)
263 assert act.varlist == (), act.varlist
265 if not isinstance(act, SCons.Action._ActionAction):
266 # only valid cmdstrfunc is None
267 def none(a) -> None:
268 pass
270 test_varlist(pos_callback, none, cmd, None, **kw)
271 else:
272 # _ActionAction should have set these
273 assert hasattr(act, 'strfunction')
274 assert act.cmdstr is _null, act.cmdstr
275 assert act.presub is _null, act.presub
276 assert act.chdir is None, act.chdir
277 assert act.exitstatfunc is SCons.Action.default_exitstatfunc, \
278 act.exitstatfunc
280 def cmdstr(a) -> None:
281 assert hasattr(a, 'strfunction')
282 assert a.cmdstr == 'cmdstr', a.cmdstr
284 test_varlist(pos_callback, cmdstr, cmd, 'cmdstr', **kw)
286 def fun() -> None:
287 pass
289 def strfun(a, fun=fun) -> None:
290 assert a.strfunction is fun, a.strfunction
291 assert a.cmdstr == _null, a.cmdstr
293 test_varlist(pos_callback, strfun, cmd, fun, **kw)
295 def none(a) -> None:
296 assert hasattr(a, 'strfunction')
297 assert a.cmdstr is None, a.cmdstr
299 test_varlist(pos_callback, none, cmd, None, **kw)
301 # Test handling of bad cmdstrfunc arguments
302 try:
303 a = SCons.Action.Action(cmd, [], **kw)
304 except SCons.Errors.UserError as e:
305 s = str(e)
306 m = 'Invalid command display variable'
307 assert s.find(m) != -1, 'Unexpected string: %s' % s
308 else:
309 raise Exception("did not catch expected UserError")
311 return act
314 class ActionTestCase(unittest.TestCase):
315 """Test the Action() factory function"""
317 def test_FunctionAction(self) -> None:
318 """Test the Action() factory's creation of FunctionAction objects."""
320 def foo() -> None:
321 pass
323 def func_action(a, foo=foo) -> None:
324 assert isinstance(a, SCons.Action.FunctionAction), a
325 assert a.execfunction == foo, a.execfunction
327 test_positional_args(func_action, foo)
328 # a singleton list returns the contained action
329 test_positional_args(func_action, [foo])
331 def test_CommandAction(self) -> None:
332 """Test the Action() factory's creation of CommandAction objects."""
334 def cmd_action(a) -> None:
335 assert isinstance(a, SCons.Action.CommandAction), a
336 assert a.cmd_list == "string", a.cmd_list
338 test_positional_args(cmd_action, "string")
339 # a singleton list returns the contained action
340 test_positional_args(cmd_action, ["string"])
342 def line_action(a) -> None:
343 assert isinstance(a, SCons.Action.CommandAction), a
344 assert a.cmd_list == ["explicit", "command", "line"], a.cmd_list
346 test_positional_args(line_action, [["explicit", "command", "line"]])
348 def test_ListAction(self) -> None:
349 """Test the Action() factory's creation of ListAction objects."""
351 a1 = SCons.Action.Action(["x", "y", "z", ["a", "b", "c"]])
352 assert isinstance(a1, SCons.Action.ListAction), f"a1 is {type(a1)}"
353 assert a1.varlist == (), a1.varlist
354 assert isinstance(a1.list[0], SCons.Action.CommandAction), a1.list[0]
355 assert a1.list[0].cmd_list == "x", a1.list[0].cmd_list
356 assert isinstance(a1.list[1], SCons.Action.CommandAction), a1.list[1]
357 assert a1.list[1].cmd_list == "y", a1.list[1].cmd_list
358 assert isinstance(a1.list[2], SCons.Action.CommandAction), a1.list[2]
359 assert a1.list[2].cmd_list == "z", a1.list[2].cmd_list
360 assert isinstance(a1.list[3], SCons.Action.CommandAction), a1.list[3]
361 assert a1.list[3].cmd_list == ["a", "b", "c"], a1.list[3].cmd_list
363 a2 = SCons.Action.Action("x\ny\nz")
364 assert isinstance(a2, SCons.Action.ListAction), f"a2 is {type(a2)}"
365 assert a2.varlist == (), a2.varlist
366 assert isinstance(a2.list[0], SCons.Action.CommandAction), a2.list[0]
367 assert a2.list[0].cmd_list == "x", a2.list[0].cmd_list
368 assert isinstance(a2.list[1], SCons.Action.CommandAction), a2.list[1]
369 assert a2.list[1].cmd_list == "y", a2.list[1].cmd_list
370 assert isinstance(a2.list[2], SCons.Action.CommandAction), a2.list[2]
371 assert a2.list[2].cmd_list == "z", a2.list[2].cmd_list
373 def foo() -> None:
374 pass
376 a3 = SCons.Action.Action(["x", foo, "z"])
377 assert isinstance(a3, SCons.Action.ListAction), f"a3 is {type(a3)}"
378 assert a3.varlist == (), a3.varlist
379 assert isinstance(a3.list[0], SCons.Action.CommandAction), a3.list[0]
380 assert a3.list[0].cmd_list == "x", a3.list[0].cmd_list
381 assert isinstance(a3.list[1], SCons.Action.FunctionAction), a3.list[1]
382 assert a3.list[1].execfunction == foo, a3.list[1].execfunction
383 assert isinstance(a3.list[2], SCons.Action.CommandAction), a3.list[2]
384 assert a3.list[2].cmd_list == "z", a3.list[2].cmd_list
386 a4 = SCons.Action.Action(["x", "y"], strfunction=foo)
387 assert isinstance(a4, SCons.Action.ListAction), f"a4 is {type(a4)}"
388 assert a4.varlist == (), a4.varlist
389 assert isinstance(a4.list[0], SCons.Action.CommandAction), a4.list[0]
390 assert a4.list[0].cmd_list == "x", a4.list[0].cmd_list
391 assert a4.list[0].strfunction == foo, a4.list[0].strfunction
392 assert isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1]
393 assert a4.list[1].cmd_list == "y", a4.list[1].cmd_list
394 assert a4.list[1].strfunction == foo, a4.list[1].strfunction
396 a5 = SCons.Action.Action("x\ny", strfunction=foo)
397 assert isinstance(a5, SCons.Action.ListAction), f"a5 is {type(a5)}"
398 assert a5.varlist == (), a5.varlist
399 assert isinstance(a5.list[0], SCons.Action.CommandAction), a5.list[0]
400 assert a5.list[0].cmd_list == "x", a5.list[0].cmd_list
401 assert a5.list[0].strfunction == foo, a5.list[0].strfunction
402 assert isinstance(a5.list[1], SCons.Action.CommandAction), a5.list[1]
403 assert a5.list[1].cmd_list == "y", a5.list[1].cmd_list
404 assert a5.list[1].strfunction == foo, a5.list[1].strfunction
406 a6 = SCons.Action.Action(["action with space"])
407 assert isinstance(a6, SCons.Action.CommandAction), f"a6 is {type(a6)}"
409 def test_CommandGeneratorAction(self) -> None:
410 """Test the Action factory's creation of CommandGeneratorAction objects."""
412 def foo() -> None: pass
414 def gen_action(a, foo=foo) -> None:
415 assert isinstance(a, SCons.Action.CommandGeneratorAction), a
416 assert a.generator is foo, a.generator
418 test_positional_args(gen_action, foo, generator=1)
420 def test_LazyCmdGeneratorAction(self) -> None:
421 """Test the Action factory's creation of lazy CommandGeneratorAction objects."""
423 def lazy_action(a) -> None:
424 assert isinstance(a, SCons.Action.LazyAction), a
425 assert a.var == "FOO", a.var
426 assert a.cmd_list == "${FOO}", a.cmd_list
428 test_positional_args(lazy_action, "$FOO")
429 test_positional_args(lazy_action, "${FOO}")
431 def test_no_action(self) -> None:
432 """Test when the Action() factory can't create an action object."""
434 try:
435 a5 = SCons.Action.Action(1)
436 except TypeError:
437 pass
438 else:
439 assert 0, "Should have thrown a TypeError creating Action from an int."
441 def test_reentrance(self) -> None:
442 """Test the Action factory when the action is already an Action object."""
444 a1 = SCons.Action.Action("foo")
445 a2 = SCons.Action.Action(a1)
446 assert a2 is a1, a2
449 class _ActionActionTestCase(unittest.TestCase):
451 def test__init__(self) -> None:
452 """Test creation of _ActionAction objects."""
454 def func1() -> None:
455 pass
457 def func2() -> None:
458 pass
460 def func3() -> None:
461 pass
463 a = SCons.Action._ActionAction()
464 assert not hasattr(a, 'strfunction')
465 assert a.cmdstr is _null, a.cmdstr
466 assert a.varlist == (), a.varlist
467 assert a.presub is _null, a.presub
468 assert a.chdir is None, a.chdir
469 assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc
471 assert SCons.Action._ActionAction(kwarg=1)
472 assert not hasattr(a, 'kwarg')
473 assert not hasattr(a, 'strfunction')
474 assert a.cmdstr is _null, a.cmdstr
475 assert a.varlist == (), a.varlist
476 assert a.presub is _null, a.presub
477 assert a.chdir is None, a.chdir
478 assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc
480 a = SCons.Action._ActionAction(strfunction=func1)
481 assert a.strfunction is func1, a.strfunction
483 a = SCons.Action._ActionAction(strfunction=None)
484 assert not hasattr(a, 'strfunction')
485 assert a.cmdstr is None, a.cmdstr
487 a = SCons.Action._ActionAction(cmdstr='cmdstr')
488 assert not hasattr(a, 'strfunction')
489 assert a.cmdstr == 'cmdstr', a.cmdstr
491 a = SCons.Action._ActionAction(cmdstr=None)
492 assert not hasattr(a, 'strfunction')
493 assert a.cmdstr is None, a.cmdstr
495 t = ('a', 'b', 'c')
496 a = SCons.Action._ActionAction(varlist=t)
497 assert a.varlist == t, a.varlist
499 a = SCons.Action._ActionAction(presub=func1)
500 assert a.presub is func1, a.presub
502 a = SCons.Action._ActionAction(chdir=1)
503 assert a.chdir == 1, a.chdir
505 a = SCons.Action._ActionAction(exitstatfunc=func1)
506 assert a.exitstatfunc is func1, a.exitstatfunc
508 a = SCons.Action._ActionAction(
509 # alphabetical order ...
510 chdir='x',
511 cmdstr='cmdstr',
512 exitstatfunc=func3,
513 presub=func2,
514 strfunction=func1,
515 varlist=t,
517 assert a.chdir == 'x', a.chdir
518 assert a.cmdstr == 'cmdstr', a.cmdstr
519 assert a.exitstatfunc is func3, a.exitstatfunc
520 assert a.presub is func2, a.presub
521 assert a.strfunction is func1, a.strfunction
522 assert a.varlist is t, a.varlist
524 def test_dup_keywords(self):
525 """Test handling of both cmdstr and strfunction arguments."""
527 def func() -> None:
528 pass
530 try:
531 a = SCons.Action.Action('foo', cmdstr='string', strfunction=func)
532 except SCons.Errors.UserError as e:
533 s = str(e)
534 m = 'Cannot have both strfunction and cmdstr args to Action()'
535 assert s.find(m) != -1, 'Unexpected string: %s' % s
536 else:
537 raise Exception("did not catch expected UserError")
539 def test___cmp__(self) -> None:
540 """Test Action comparison."""
542 a1 = SCons.Action.Action("x")
543 a2 = SCons.Action.Action("x")
544 assert a1 == a2
545 a3 = SCons.Action.Action("y")
546 assert a1 != a3
547 assert a2 != a3
549 def test_print_cmd_lines(self) -> None:
550 """Test the print_cmd_lines() method."""
552 save_stdout = sys.stdout
554 try:
555 def execfunc(target, source, env) -> None:
556 pass
558 a = SCons.Action.Action(execfunc)
560 sio = io.StringIO()
561 sys.stdout = sio
562 a.print_cmd_line("foo bar", None, None, None)
563 s = sio.getvalue()
564 assert s == "foo bar\n", s
566 finally:
567 sys.stdout = save_stdout
569 def test___call__(self) -> None:
570 """Test calling an Action."""
572 save_stdout = sys.stdout
574 save_print_actions = SCons.Action.print_actions
575 save_print_actions_presub = SCons.Action.print_actions_presub
576 save_execute_actions = SCons.Action.execute_actions
577 # SCons.Action.print_actions = 0
579 test = TestCmd.TestCmd(workdir='')
580 test.subdir('sub', 'xyz')
581 os.chdir(test.workpath())
583 try:
584 env = Environment()
586 def execfunc(target, source, env) -> int:
587 assert isinstance(target, list), type(target)
588 assert isinstance(source, list), type(source)
589 return 7
591 a = SCons.Action.Action(execfunc)
593 def firstfunc(target, source, env) -> int:
594 assert isinstance(target, list), type(target)
595 assert isinstance(source, list), type(source)
596 return 0
598 def lastfunc(target, source, env) -> int:
599 assert isinstance(target, list), type(target)
600 assert isinstance(source, list), type(source)
601 return 9
603 b = SCons.Action.Action([firstfunc, execfunc, lastfunc])
605 sio = io.StringIO()
606 sys.stdout = sio
607 result = a("out", "in", env)
608 assert result.status == 7, result
609 s = sio.getvalue()
610 assert s == "execfunc(['out'], ['in'])\n", s
612 a.chdir = 'xyz'
613 expect = "os.chdir(%s)\nexecfunc(['out'], ['in'])\nos.chdir(%s)\n"
615 sio = io.StringIO()
616 sys.stdout = sio
617 result = a("out", "in", env)
618 assert result.status == 7, result.status
619 s = sio.getvalue()
620 assert s == expect % (repr('xyz'), repr(test.workpath())), s
622 sio = io.StringIO()
623 sys.stdout = sio
624 result = a("out", "in", env, chdir='sub')
625 assert result.status == 7, result.status
626 s = sio.getvalue()
627 assert s == expect % (repr('sub'), repr(test.workpath())), s
629 a.chdir = None
631 sio = io.StringIO()
632 sys.stdout = sio
633 result = b("out", "in", env)
634 assert result.status == 7, result.status
635 s = sio.getvalue()
636 assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\n", s
638 SCons.Action.execute_actions = 0
640 sio = io.StringIO()
641 sys.stdout = sio
642 result = a("out", "in", env)
643 assert result == 0, result
644 s = sio.getvalue()
645 assert s == "execfunc(['out'], ['in'])\n", s
647 sio = io.StringIO()
648 sys.stdout = sio
649 result = b("out", "in", env)
650 assert result == 0, result
651 s = sio.getvalue()
652 assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\nlastfunc(['out'], ['in'])\n", s
654 SCons.Action.print_actions_presub = 1
655 SCons.Action.execute_actions = 1
657 sio = io.StringIO()
658 sys.stdout = sio
659 result = a("out", "in", env)
660 assert result.status == 7, result.status
661 s = sio.getvalue()
662 assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
664 sio = io.StringIO()
665 sys.stdout = sio
666 result = a("out", "in", env, presub=0)
667 assert result.status == 7, result.status
668 s = sio.getvalue()
669 assert s == "execfunc(['out'], ['in'])\n", s
671 sio = io.StringIO()
672 sys.stdout = sio
673 result = a("out", "in", env, presub=1)
674 assert result.status == 7, result.status
675 s = sio.getvalue()
676 assert s == "Building 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"], "in", env, presub=1)
681 assert result.status == 7, result.status
682 s = sio.getvalue()
683 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
685 sio = io.StringIO()
686 sys.stdout = sio
687 result = b(["out", "list"], "in", env, presub=1)
688 assert result.status == 7, result.status
689 s = sio.getvalue()
690 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
692 a2 = SCons.Action.Action(execfunc)
694 sio = io.StringIO()
695 sys.stdout = sio
696 result = a2("out", "in", env)
697 assert result.status == 7, result.status
698 s = sio.getvalue()
699 assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s
701 sio = io.StringIO()
702 sys.stdout = sio
703 result = a2("out", "in", env, presub=0)
704 assert result.status == 7, result.status
705 s = sio.getvalue()
706 assert s == "execfunc(['out'], ['in'])\n", s
708 SCons.Action.execute_actions = 0
710 sio = io.StringIO()
711 sys.stdout = sio
712 result = a2("out", "in", env, presub=0)
713 assert result == 0, result
714 s = sio.getvalue()
715 assert s == "execfunc(['out'], ['in'])\n", s
717 sio = io.StringIO()
718 sys.stdout = sio
719 result = a("out", "in", env, presub=0, execute=1, show=0)
720 assert result.status == 7, result.status
721 s = sio.getvalue()
722 assert s == '', s
724 sys.stdout = save_stdout
725 exitstatfunc_result = []
727 def exitstatfunc(stat, result=exitstatfunc_result):
728 result.append(stat)
729 return stat
731 result = a("out", "in", env, exitstatfunc=exitstatfunc)
732 assert result == 0, result
733 assert exitstatfunc_result == [], exitstatfunc_result
735 result = a("out", "in", env, execute=1, exitstatfunc=exitstatfunc)
736 assert result.status == 7, result.status
737 assert exitstatfunc_result == [7], exitstatfunc_result
739 SCons.Action.execute_actions = 1
741 result = []
743 def my_print_cmd_line(s, target, source, env, result=result) -> None:
744 result.append(s)
746 env['PRINT_CMD_LINE_FUNC'] = my_print_cmd_line
747 a("output", "input", env)
748 assert result == ["execfunc(['output'], ['input'])"], result
751 finally:
752 sys.stdout = save_stdout
753 SCons.Action.print_actions = save_print_actions
754 SCons.Action.print_actions_presub = save_print_actions_presub
755 SCons.Action.execute_actions = save_execute_actions
757 def test_presub_lines(self) -> None:
758 """Test the presub_lines() method."""
760 env = Environment()
761 a = SCons.Action.Action("x")
762 s = a.presub_lines(env)
763 assert s == ['x'], s
765 a = SCons.Action.Action(["y", "z"])
766 s = a.presub_lines(env)
767 assert s == ['y', 'z'], s
769 def func() -> None:
770 pass
772 a = SCons.Action.Action(func)
773 s = a.presub_lines(env)
774 assert s == ["func(target, source, env)"], s
776 def gen(target, source, env, for_signature):
777 return 'generat' + env.get('GEN', 'or')
779 a = SCons.Action.Action(gen, generator=1)
780 s = a.presub_lines(env)
781 assert s == ["generator"], s
782 s = a.presub_lines(Environment(GEN='ed'))
783 assert s == ["generated"], s
785 a = SCons.Action.Action("$ACT")
786 s = a.presub_lines(env)
787 assert s == [''], s
788 s = a.presub_lines(Environment(ACT='expanded action'))
789 assert s == ['expanded action'], s
791 def test_add(self) -> None:
792 """Test adding Actions to stuff."""
794 # Adding actions to other Actions or to stuff that can
795 # be converted into an Action should produce a ListAction
796 # containing all the Actions.
797 def bar():
798 return None
800 baz = SCons.Action.Action(bar, generator=1)
801 act1 = SCons.Action.Action('foo bar')
802 act2 = SCons.Action.Action(['foo', bar])
804 sum = act1 + act2
805 assert isinstance(sum, SCons.Action.ListAction), str(sum)
806 assert len(sum.list) == 3, len(sum.list)
807 assert [isinstance(x, SCons.Action.ActionBase) for x in sum.list] == [1, 1, 1]
809 sum = act1 + act1
810 assert isinstance(sum, SCons.Action.ListAction), str(sum)
811 assert len(sum.list) == 2, len(sum.list)
813 sum = act2 + act2
814 assert isinstance(sum, SCons.Action.ListAction), str(sum)
815 assert len(sum.list) == 4, len(sum.list)
817 # Should also be able to add command generators to each other
818 # or to actions
819 sum = baz + baz
820 assert isinstance(sum, SCons.Action.ListAction), str(sum)
821 assert len(sum.list) == 2, len(sum.list)
823 sum = baz + act1
824 assert isinstance(sum, SCons.Action.ListAction), str(sum)
825 assert len(sum.list) == 2, len(sum.list)
827 sum = act2 + baz
828 assert isinstance(sum, SCons.Action.ListAction), str(sum)
829 assert len(sum.list) == 3, len(sum.list)
831 # Also should be able to add Actions to anything that can
832 # be converted into an action.
833 sum = act1 + bar
834 assert isinstance(sum, SCons.Action.ListAction), str(sum)
835 assert len(sum.list) == 2, len(sum.list)
836 assert isinstance(sum.list[1], SCons.Action.FunctionAction)
838 sum = 'foo bar' + act2
839 assert isinstance(sum, SCons.Action.ListAction), str(sum)
840 assert len(sum.list) == 3, len(sum.list)
841 assert isinstance(sum.list[0], SCons.Action.CommandAction)
843 sum = ['foo', 'bar'] + act1
844 assert isinstance(sum, SCons.Action.ListAction), str(sum)
845 assert len(sum.list) == 3, sum.list
846 assert isinstance(sum.list[0], SCons.Action.CommandAction)
847 assert isinstance(sum.list[1], SCons.Action.CommandAction)
849 sum = act2 + [baz, bar]
850 assert isinstance(sum, SCons.Action.ListAction), str(sum)
851 assert len(sum.list) == 4, len(sum.list)
852 assert isinstance(sum.list[2], SCons.Action.CommandGeneratorAction)
853 assert isinstance(sum.list[3], SCons.Action.FunctionAction)
855 # OK to add None on either side (should be ignored)
856 sum = act1 + None
857 assert sum == act1
859 sum = None + act1
860 assert sum == act1
862 try:
863 sum = act2 + 1
864 except TypeError:
865 pass
866 else:
867 assert 0, "Should have thrown a TypeError adding to an int."
869 try:
870 sum = 1 + act2
871 except TypeError:
872 pass
873 else:
874 assert 0, "Should have thrown a TypeError adding to an int."
877 class CommandActionTestCase(unittest.TestCase):
879 def test___init__(self) -> None:
880 """Test creation of a command Action."""
882 a = SCons.Action.CommandAction(["xyzzy"])
883 assert a.cmd_list == ["xyzzy"], a.cmd_list
884 assert a.cmdstr is _null, a.cmdstr
886 a = SCons.Action.CommandAction(["abra"], cmdstr="cadabra")
887 assert a.cmd_list == ["abra"], a.cmd_list
888 assert a.cmdstr == "cadabra", a.cmdstr
890 def test___str__(self) -> None:
891 """Test fetching the pre-substitution string for command Actions."""
893 env = Environment()
894 act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
895 s = str(act)
896 assert s == 'xyzzy $TARGET $SOURCE', s
898 act = SCons.Action.CommandAction(['xyzzy',
899 '$TARGET', '$SOURCE',
900 '$TARGETS', '$SOURCES'])
901 s = str(act)
902 assert s == "xyzzy $TARGET $SOURCE $TARGETS $SOURCES", s
904 def test_genstring(self) -> None:
905 """Test the genstring() method for command Actions."""
907 env = Environment()
908 t1 = DummyNode('t1')
909 t2 = DummyNode('t2')
910 s1 = DummyNode('s1')
911 s2 = DummyNode('s2')
912 act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
913 expect = 'xyzzy $TARGET $SOURCE'
914 s = act.genstring([], [], env)
915 assert s == expect, s
916 s = act.genstring([t1], [s1], env)
917 assert s == expect, s
918 s = act.genstring([t1, t2], [s1, s2], env)
919 assert s == expect, s
921 act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
922 expect = 'xyzzy $TARGETS $SOURCES'
923 s = act.genstring([], [], env)
924 assert s == expect, s
925 s = act.genstring([t1], [s1], env)
926 assert s == expect, s
927 s = act.genstring([t1, t2], [s1, s2], env)
928 assert s == expect, s
930 act = SCons.Action.CommandAction(['xyzzy',
931 '$TARGET', '$SOURCE',
932 '$TARGETS', '$SOURCES'])
933 expect = "xyzzy $TARGET $SOURCE $TARGETS $SOURCES"
934 s = act.genstring([], [], env)
935 assert s == expect, s
936 s = act.genstring([t1], [s1], env)
937 assert s == expect, s
938 s = act.genstring([t1, t2], [s1, s2], env)
939 assert s == expect, s
941 def test_strfunction(self) -> None:
942 """Test fetching the string representation of command Actions."""
944 env = Environment()
945 t1 = DummyNode('t1')
946 t2 = DummyNode('t2')
947 s1 = DummyNode('s1')
948 s2 = DummyNode('s2')
949 act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
950 s = act.strfunction([], [], env)
951 assert s == 'xyzzy', s
952 s = act.strfunction([t1], [s1], env)
953 assert s == 'xyzzy t1 s1', s
954 s = act.strfunction([t1, t2], [s1, s2], env)
955 assert s == 'xyzzy t1 s1', s
957 act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE',
958 cmdstr='cmdstr - $SOURCE - $TARGET -')
959 s = act.strfunction([], [], env)
960 assert s == 'cmdstr - - -', s
961 s = act.strfunction([t1], [s1], env)
962 assert s == 'cmdstr - s1 - t1 -', s
963 s = act.strfunction([t1, t2], [s1, s2], env)
964 assert s == 'cmdstr - s1 - t1 -', s
966 act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
967 s = act.strfunction([], [], env)
968 assert s == 'xyzzy', s
969 s = act.strfunction([t1], [s1], env)
970 assert s == 'xyzzy t1 s1', s
971 s = act.strfunction([t1, t2], [s1, s2], env)
972 assert s == 'xyzzy t1 t2 s1 s2', s
974 act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES',
975 cmdstr='cmdstr = $SOURCES = $TARGETS =')
976 s = act.strfunction([], [], env)
977 assert s == 'cmdstr = = =', s
978 s = act.strfunction([t1], [s1], env)
979 assert s == 'cmdstr = s1 = t1 =', s
980 s = act.strfunction([t1, t2], [s1, s2], env)
981 assert s == 'cmdstr = s1 s2 = t1 t2 =', s
983 act = SCons.Action.CommandAction(['xyzzy',
984 '$TARGET', '$SOURCE',
985 '$TARGETS', '$SOURCES'])
986 s = act.strfunction([], [], env)
987 assert s == 'xyzzy', s
988 s = act.strfunction([t1], [s1], env)
989 assert s == 'xyzzy t1 s1 t1 s1', s
990 s = act.strfunction([t1, t2], [s1, s2], env)
991 assert s == 'xyzzy t1 s1 t1 t2 s1 s2', s
993 act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES',
994 cmdstr='cmdstr\t$TARGETS\n$SOURCES ')
996 s = act.strfunction([], [], env)
997 assert s == 'cmdstr\t\n ', s
998 s = act.strfunction([t1], [s1], env)
999 assert s == 'cmdstr\tt1\ns1 ', s
1000 s = act.strfunction([t1, t2], [s1, s2], env)
1001 assert s == 'cmdstr\tt1 t2\ns1 s2 ', s
1003 def sf(target, source, env) -> str:
1004 return "sf was called"
1006 act = SCons.Action.CommandAction('foo', strfunction=sf)
1007 s = act.strfunction([], [], env)
1008 assert s == "sf was called", s
1010 class actclass1:
1011 def __init__(self, targets, sources, env) -> None:
1012 pass
1014 def __call__(self) -> int:
1015 return 1
1017 class actclass2:
1018 def __init__(self, targets, sources, env) -> None:
1019 self.strfunction = 5
1021 def __call__(self) -> int:
1022 return 2
1024 class actclass3:
1025 def __init__(self, targets, sources, env) -> None:
1026 pass
1028 def __call__(self) -> int:
1029 return 3
1031 def strfunction(self, targets, sources, env) -> str:
1032 return 'actclass3 on %s to get %s' % (str(sources[0]),
1033 str(targets[0]))
1035 class actclass4:
1036 def __init__(self, targets, sources, env) -> None:
1037 pass
1039 def __call__(self) -> int:
1040 return 4
1042 strfunction = None
1044 act1 = SCons.Action.Action(actclass1([t1], [s1], env))
1045 s = act1.strfunction([t1], [s1], env)
1046 assert s == 'actclass1(["t1"], ["s1"])', s
1048 act2 = SCons.Action.Action(actclass2([t1], [s1], env))
1049 s = act2.strfunction([t1], [s1], env)
1050 assert s == 'actclass2(["t1"], ["s1"])', s
1052 act3 = SCons.Action.Action(actclass3([t1], [s1], env))
1053 s = act3.strfunction([t1], [s1], env)
1054 assert s == 'actclass3 on s1 to get t1', s
1056 act4 = SCons.Action.Action(actclass4([t1], [s1], env))
1057 s = act4.strfunction([t1], [s1], env)
1058 assert s is None, s
1060 act = SCons.Action.CommandAction("@foo bar")
1061 s = act.strfunction([], [], env)
1062 assert s == "", s
1064 act = SCons.Action.CommandAction("@-foo bar")
1065 s = act.strfunction([], [], env)
1066 assert s == "", s
1068 act = SCons.Action.CommandAction("-@foo bar")
1069 s = act.strfunction([], [], env)
1070 assert s == "", s
1072 act = SCons.Action.CommandAction("-foo bar")
1073 s = act.strfunction([], [], env)
1074 assert s == "foo bar", s
1076 act = SCons.Action.CommandAction("@ foo bar")
1077 s = act.strfunction([], [], env)
1078 assert s == "", s
1080 act = SCons.Action.CommandAction("@- foo bar")
1081 s = act.strfunction([], [], env)
1082 assert s == "", s
1084 act = SCons.Action.CommandAction("-@ foo bar")
1085 s = act.strfunction([], [], env)
1086 assert s == "", s
1088 act = SCons.Action.CommandAction("- foo bar")
1089 s = act.strfunction([], [], env)
1090 assert s == "foo bar", s
1092 def test_execute(self) -> None:
1093 """Test execution of command Actions."""
1095 try:
1096 env = self.env
1097 except AttributeError:
1098 env = Environment()
1100 cmd1 = r'%s %s %s xyzzy' % (_python_, act_py, outfile)
1102 act = SCons.Action.CommandAction(cmd1)
1103 r = act([], [], env.Clone())
1104 assert r == 0
1105 c = test.read(outfile, 'r')
1106 assert c == "act.py: 'xyzzy'\n", c
1108 cmd2 = r'%s %s %s $TARGET' % (_python_, act_py, outfile)
1110 act = SCons.Action.CommandAction(cmd2)
1111 r = act(DummyNode('foo'), [], env.Clone())
1112 assert r == 0
1113 c = test.read(outfile, 'r')
1114 assert c == "act.py: 'foo'\n", c
1116 cmd3 = r'%s %s %s ${TARGETS}' % (_python_, act_py, outfile)
1118 act = SCons.Action.CommandAction(cmd3)
1119 r = act(list(map(DummyNode, ['aaa', 'bbb'])), [], env.Clone())
1120 assert r == 0
1121 c = test.read(outfile, 'r')
1122 assert c == "act.py: 'aaa' 'bbb'\n", c
1124 cmd4 = r'%s %s %s $SOURCES' % (_python_, act_py, outfile)
1126 act = SCons.Action.CommandAction(cmd4)
1127 r = act([], [DummyNode('one'), DummyNode('two')], env.Clone())
1128 assert r == 0
1129 c = test.read(outfile, 'r')
1130 assert c == "act.py: 'one' 'two'\n", c
1132 cmd4 = r'%s %s %s ${SOURCES[:2]}' % (_python_, act_py, outfile)
1134 act = SCons.Action.CommandAction(cmd4)
1135 sources = [DummyNode('three'), DummyNode('four'), DummyNode('five')]
1136 env2 = env.Clone()
1137 r = act([], source=sources, env=env2)
1138 assert r == 0
1139 c = test.read(outfile, 'r')
1140 assert c == "act.py: 'three' 'four'\n", c
1142 cmd5 = r'%s %s %s $TARGET XYZZY' % (_python_, act_py, outfile)
1144 act = SCons.Action.CommandAction(cmd5)
1145 env5 = Environment()
1146 if 'ENV' in scons_env:
1147 env5['ENV'] = scons_env['ENV']
1148 PATH = scons_env['ENV'].get('PATH', '')
1149 else:
1150 env5['ENV'] = {}
1151 PATH = ''
1153 env5['ENV']['XYZZY'] = 'xyzzy'
1154 r = act(target=DummyNode('out5'), source=[], env=env5)
1156 act = SCons.Action.CommandAction(cmd5)
1157 r = act(target=DummyNode('out5'),
1158 source=[],
1159 env=env.Clone(ENV={'XYZZY': 'xyzzy5',
1160 'PATH': PATH}))
1161 assert r == 0
1162 c = test.read(outfile, 'r')
1163 assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy5'\n", c
1165 class Obj:
1166 def __init__(self, str) -> None:
1167 self._str = str
1169 def __str__(self) -> str:
1170 return self._str
1172 def rfile(self):
1173 return self
1175 def get_subst_proxy(self):
1176 return self
1178 cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (_python_, act_py, outfile)
1180 act = SCons.Action.CommandAction(cmd6)
1181 r = act(target=[Obj('111'), Obj('222')],
1182 source=[Obj('333'), Obj('444'), Obj('555')],
1183 env=env.Clone())
1184 assert r == 0
1185 c = test.read(outfile, 'r')
1186 assert c == "act.py: '222' '111' '333' '444'\n", c
1188 if os.name == 'nt':
1189 # NT treats execs of directories and non-executable files
1190 # as "file not found" errors
1191 expect_nonexistent = 1
1192 expect_nonexecutable_file = 1
1193 expect_nonexecutable_dir = 1
1194 elif sys.platform == 'cygwin':
1195 expect_nonexistent = 127
1196 # Newer cygwin seems to return 126 for following
1197 expect_nonexecutable_file = 126
1198 expect_nonexecutable_dir = 127
1199 else:
1200 expect_nonexistent = 127
1201 expect_nonexecutable_file = 126
1202 expect_nonexecutable_dir = 126
1204 # Test that a nonexistent command returns 127
1205 act = SCons.Action.CommandAction(python + "_no_such_command_")
1206 r = act([], [], env.Clone(out=outfile))
1207 assert r.status == expect_nonexistent, r.status
1209 # Test that trying to execute a directory returns 126
1210 dir, tail = os.path.split(python)
1211 act = SCons.Action.CommandAction(dir)
1212 r = act([], [], env.Clone(out=outfile))
1213 assert r.status == expect_nonexecutable_file, r.status
1215 # Test that trying to execute a non-executable file returns 126
1216 act = SCons.Action.CommandAction(outfile)
1217 r = act([], [], env.Clone(out=outfile))
1218 assert r.status == expect_nonexecutable_dir, r.status
1220 act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py))
1221 r = act([], [], env)
1222 assert r.status == 1, r.status
1224 act = SCons.Action.CommandAction('@%s %s 1' % (_python_, exit_py))
1225 r = act([], [], env)
1226 assert r.status == 1, r.status
1228 act = SCons.Action.CommandAction('@-%s %s 1' % (_python_, exit_py))
1229 r = act([], [], env)
1230 assert r == 0, r
1232 act = SCons.Action.CommandAction('-%s %s 1' % (_python_, exit_py))
1233 r = act([], [], env)
1234 assert r == 0, r
1236 act = SCons.Action.CommandAction('@ %s %s 1' % (_python_, exit_py))
1237 r = act([], [], env)
1238 assert r.status == 1, r.status
1240 act = SCons.Action.CommandAction('@- %s %s 1' % (_python_, exit_py))
1241 r = act([], [], env)
1242 assert r == 0, r
1244 act = SCons.Action.CommandAction('- %s %s 1' % (_python_, exit_py))
1245 r = act([], [], env)
1246 assert r == 0, r
1248 def test_set_handler(self) -> None:
1249 """Test setting the command handler..."""
1251 class Test:
1252 def __init__(self) -> None:
1253 self.executed = 0
1255 t = Test()
1257 def func(sh, escape, cmd, args, env, test=t) -> int:
1258 test.executed = args
1259 test.shell = sh
1260 return 0
1262 def escape_func(cmd):
1263 return '**' + cmd + '**'
1265 class LiteralStr:
1266 def __init__(self, x) -> None:
1267 self.data = x
1269 def __str__(self) -> str:
1270 return self.data
1272 def escape(self, escape_func):
1273 return escape_func(self.data)
1275 def is_literal(self) -> bool:
1276 return True
1278 a = SCons.Action.CommandAction(["xyzzy"])
1279 e = Environment(SPAWN=func)
1280 a([], [], e)
1281 assert t.executed == ['xyzzy'], t.executed
1283 a = SCons.Action.CommandAction(["xyzzy"])
1284 e = Environment(SPAWN='$FUNC', FUNC=func)
1285 a([], [], e)
1286 assert t.executed == ['xyzzy'], t.executed
1288 a = SCons.Action.CommandAction(["xyzzy"])
1289 e = Environment(SPAWN=func, SHELL='fake shell')
1290 a([], [], e)
1291 assert t.executed == ['xyzzy'], t.executed
1292 assert t.shell == 'fake shell', t.shell
1294 a = SCons.Action.CommandAction([LiteralStr("xyzzy")])
1295 e = Environment(SPAWN=func, ESCAPE=escape_func)
1296 a([], [], e)
1297 assert t.executed == ['**xyzzy**'], t.executed
1299 def test_get_contents(self) -> None:
1300 """Test fetching the contents of a command Action."""
1302 def CmdGen(target, source, env, for_signature) -> str:
1303 assert for_signature
1304 return "%s %s" % \
1305 (env["foo"], env["bar"])
1307 # The number 1 is there to make sure all args get converted to strings.
1308 a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$(", "$bar",
1309 "$)", "stuff", "$)", "|", "$baz", 1])
1310 c = a.get_contents(target=[], source=[],
1311 env=Environment(foo='FFF', bar='BBB',
1312 baz=CmdGen))
1313 assert c == b"| | FFF BBB 1", c
1315 # Make sure that CommandActions use an Environment's
1316 # subst_target_source() method for substitution.
1317 class SpecialEnvironment(Environment):
1318 def subst_target_source(self, strSubst, raw: int=0, target=[], source=[]):
1319 return 'subst_target_source: ' + strSubst
1321 c = a.get_contents(target=DummyNode('ttt'), source=DummyNode('sss'),
1322 env=SpecialEnvironment(foo='GGG', bar='CCC',
1323 baz='ZZZ'))
1324 assert c == b'subst_target_source: | $( $foo | $( $bar $) stuff $) | $baz 1', c
1326 # We've discussed using the real target and source names in a
1327 # CommandAction's signature contents. This would have have the
1328 # advantage of recompiling when a file's name changes (keeping
1329 # debug info current), but it would currently break repository
1330 # logic that will change the file name based on whether the
1331 # files come from a repository or locally. If we ever move to
1332 # that scheme, then all of the '__t1__' and '__s6__' file names
1333 # in the asserts below would change to 't1' and 's6' and the
1334 # like.
1335 t = list(map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6']))
1336 s = list(map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6']))
1337 env = Environment()
1339 a = SCons.Action.CommandAction(["$TARGET"])
1340 c = a.get_contents(target=t, source=s, env=env)
1341 assert c == b"t1", c
1343 a = SCons.Action.CommandAction(["$TARGETS"])
1344 c = a.get_contents(target=t, source=s, env=env)
1345 assert c == b"t1 t2 t3 t4 t5 t6", c
1347 a = SCons.Action.CommandAction(["${TARGETS[2]}"])
1348 c = a.get_contents(target=t, source=s, env=env)
1349 assert c == b"t3", c
1351 a = SCons.Action.CommandAction(["${TARGETS[3:5]}"])
1352 c = a.get_contents(target=t, source=s, env=env)
1353 assert c == b"t4 t5", c
1355 a = SCons.Action.CommandAction(["$SOURCE"])
1356 c = a.get_contents(target=t, source=s, env=env)
1357 assert c == b"s1", c
1359 a = SCons.Action.CommandAction(["$SOURCES"])
1360 c = a.get_contents(target=t, source=s, env=env)
1361 assert c == b"s1 s2 s3 s4 s5 s6", c
1363 a = SCons.Action.CommandAction(["${SOURCES[2]}"])
1364 c = a.get_contents(target=t, source=s, env=env)
1365 assert c == b"s3", c
1367 a = SCons.Action.CommandAction(["${SOURCES[3:5]}"])
1368 c = a.get_contents(target=t, source=s, env=env)
1369 assert c == b"s4 s5", c
1371 def test_get_implicit_deps(self) -> None:
1372 """Test getting the implicit dependencies of a command Action."""
1374 class SpecialEnvironment(Environment):
1375 def WhereIs(self, prog):
1376 return prog
1378 class fs:
1379 def File(name):
1380 return name
1382 env = SpecialEnvironment()
1383 a = SCons.Action.CommandAction([_python_, exit_py])
1384 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [_python_])
1386 for false_like_value in [False, 0, None, "None", "False", "0", "NONE", "FALSE", "off", "OFF", "NO", "no"]:
1387 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES=false_like_value)
1388 a = SCons.Action.CommandAction([_python_, exit_py])
1389 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [])
1391 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES="$SUBST_VALUE", SUBST_VALUE=false_like_value)
1392 a = SCons.Action.CommandAction([_python_, exit_py])
1393 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [])
1395 for true_like_value in [True, 1, "True", "TRUE", "1"]:
1396 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES=true_like_value)
1397 a = SCons.Action.CommandAction([_python_, exit_py])
1398 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [_python_])
1400 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES="$SUBST_VALUE", SUBST_VALUE=true_like_value)
1401 a = SCons.Action.CommandAction([_python_, exit_py])
1402 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [_python_])
1404 for all_like_Value in ["all", "ALL", 2, "2"]:
1405 env = SpecialEnvironment(IMPLICIT_COMMAND_DEPENDENCIES=all_like_Value)
1406 a = SCons.Action.CommandAction([_python_, exit_py])
1407 self.assertEqual(a.get_implicit_deps(target=[], source=[], env=env), [_python_, exit_py])
1410 class CommandGeneratorActionTestCase(unittest.TestCase):
1412 def factory(self, act, **kw):
1413 """Pass any keywords as a dict"""
1415 return SCons.Action.CommandGeneratorAction(act, kw)
1417 def test___init__(self) -> None:
1418 """Test creation of a command generator Action."""
1420 def f(target, source, env) -> None:
1421 pass
1423 a = self.factory(f)
1424 assert a.generator == f
1426 def test___str__(self) -> None:
1427 """Test the pre-substitution strings for command generator Actions."""
1429 def f(target, source, env, for_signature, self=self) -> str:
1430 # See if "env" is really a construction environment (or
1431 # looks like one) by accessing the FindIxes attribute.
1432 # (The Tool/mingw.py module has a generator that uses this,
1433 # and the __str__() method used to cause problems by passing
1434 # us a regular dictionary as a fallback.)
1436 env.FindIxes
1437 return "FOO"
1439 a = self.factory(f)
1440 s = str(a)
1441 assert s == 'FOO', s
1443 def test_genstring(self) -> None:
1444 """Test the command generator Action genstring() method."""
1446 def f(target, source, env, for_signature, self=self) -> str:
1447 dummy = env['dummy']
1448 self.dummy = dummy
1449 return "$FOO $TARGET $SOURCE $TARGETS $SOURCES"
1451 a = self.factory(f)
1452 self.dummy = 0
1453 s = a.genstring([], [], env=Environment(FOO='xyzzy', dummy=1))
1454 assert self.dummy == 1, self.dummy
1455 assert s == "$FOO $TARGET $SOURCE $TARGETS $SOURCES", s
1457 def test_execute(self) -> None:
1458 """Test executing a command generator Action."""
1460 def f(target, source, env, for_signature, self=self) -> str:
1461 dummy = env['dummy']
1462 self.dummy = dummy
1463 s = env.subst("$FOO")
1464 assert s == 'foo baz\nbar ack', s
1465 return "$FOO"
1467 def func_action(target, source, env, self=self) -> None:
1468 dummy = env['dummy']
1469 s = env.subst('$foo')
1470 assert s == 'bar', s
1471 self.dummy = dummy
1473 def f2(target, source, env, for_signature, f=func_action):
1474 return f
1476 def ch(sh, escape, cmd, args, env, self=self) -> None:
1477 self.cmd.append(cmd)
1478 self.args.append(args)
1480 a = self.factory(f)
1481 self.dummy = 0
1482 self.cmd = []
1483 self.args = []
1484 a([], [], env=Environment(FOO='foo baz\nbar ack',
1485 dummy=1,
1486 SPAWN=ch))
1487 assert self.dummy == 1, self.dummy
1488 assert self.cmd == ['foo', 'bar'], self.cmd
1489 assert self.args == [['foo', 'baz'], ['bar', 'ack']], self.args
1491 b = self.factory(f2)
1492 self.dummy = 0
1493 b(target=[], source=[], env=Environment(foo='bar',
1494 dummy=2))
1495 assert self.dummy == 2, self.dummy
1496 del self.dummy
1498 class DummyFile:
1499 def __init__(self, t) -> None:
1500 self.t = t
1502 def rfile(self):
1503 self.t.rfile_called = 1
1504 return self
1506 def get_subst_proxy(self):
1507 return self
1509 def f3(target, source, env, for_signature) -> str:
1510 return ''
1512 c = self.factory(f3)
1513 c(target=[], source=DummyFile(self), env=Environment())
1514 assert self.rfile_called
1516 def test_get_contents(self) -> None:
1517 """Test fetching the contents of a command generator Action."""
1519 def f(target, source, env, for_signature):
1520 foo = env['foo']
1521 bar = env['bar']
1522 assert for_signature, for_signature
1523 return [["guux", foo, "$(", "$ignore", "$)", bar,
1524 '${test("$( foo $bar $)")}']]
1526 def test(mystr) -> str:
1527 assert mystr == "$( foo $bar $)", mystr
1528 return "test"
1530 env = Environment(foo='FFF', bar='BBB',
1531 ignore='foo', test=test)
1532 a = self.factory(f)
1533 c = a.get_contents(target=[], source=[], env=env)
1534 assert c == b"guux FFF BBB test", c
1536 def test_get_contents_of_function_action(self) -> None:
1537 """Test contents of a CommandGeneratorAction-generated FunctionAction."""
1539 def LocalFunc() -> None:
1540 pass
1542 # Since the python bytecode has per version differences, we need different expected results per version
1543 func_matches = {
1544 (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'),
1545 (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1546 (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1547 (3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1548 (3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1549 (3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1550 (3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
1551 (3, 12): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00y\x00),(),()'),
1552 (3, 13): bytearray(b'0, 0, 0, 0,(),(),(\x95\x00g\x00),(),()'),
1555 meth_matches = [
1556 b"1, 1, 0, 0,(),(),(d\000\000S),(),()",
1557 b"1, 1, 0, 0,(),(),(d\x00\x00S),(),()",
1560 def f_global(target, source, env, for_signature):
1561 return SCons.Action.Action(GlobalFunc)
1563 def f_local(target, source, env, for_signature):
1564 return SCons.Action.Action(LocalFunc)
1566 env = Environment(XYZ='foo')
1568 with self.subTest():
1569 a = self.factory(f_global)
1570 c = a.get_contents(target=[], source=[], env=env)
1571 self.assertEqual(c, func_matches[sys.version_info[:2]])
1573 with self.subTest():
1574 a = self.factory(f_local)
1575 c = a.get_contents(target=[], source=[], env=env)
1576 self.assertEqual(c, func_matches[sys.version_info[:2]])
1578 def f_global2(target, source, env, for_signature):
1579 return SCons.Action.Action(GlobalFunc, varlist=['XYZ'])
1581 def f_local2(target, source, env, for_signature):
1582 return SCons.Action.Action(LocalFunc, varlist=['XYZ'])
1584 matches_foo = func_matches[sys.version_info[:2]] + b'foo'
1586 with self.subTest():
1587 a = self.factory(f_global2)
1588 c = a.get_contents(target=[], source=[], env=env)
1589 self.assertIn(c, matches_foo)
1591 with self.subTest():
1592 a = self.factory(f_local2)
1593 c = a.get_contents(target=[], source=[], env=env)
1594 self.assertIn(c, matches_foo)
1597 class FunctionActionTestCase(unittest.TestCase):
1599 def test___init__(self) -> None:
1600 """Test creation of a function Action."""
1602 def func1() -> None:
1603 pass
1605 def func2() -> None:
1606 pass
1608 def func3() -> None:
1609 pass
1611 def func4() -> None:
1612 pass
1614 a = SCons.Action.FunctionAction(func1, {})
1615 assert a.execfunction == func1, a.execfunction
1616 assert isinstance(a.strfunction, types.MethodType), type(a.strfunction)
1618 a = SCons.Action.FunctionAction(func2, {'strfunction': func3})
1619 assert a.execfunction == func2, a.execfunction
1620 assert a.strfunction == func3, a.strfunction
1622 def test___str__(self) -> None:
1623 """Test the __str__() method for function Actions."""
1625 def func1() -> None:
1626 pass
1628 a = SCons.Action.FunctionAction(func1, {})
1629 s = str(a)
1630 assert s == "func1(target, source, env)", s
1632 class class1:
1633 def __call__(self) -> None:
1634 pass
1636 a = SCons.Action.FunctionAction(class1(), {})
1637 s = str(a)
1638 assert s == "class1(target, source, env)", s
1640 def test_execute(self) -> None:
1641 """Test executing a function Action."""
1643 self.inc = 0
1645 def f(target, source, env) -> int:
1646 s = env['s']
1647 s.inc = s.inc + 1
1648 s.target = target
1649 s.source = source
1650 assert env.subst("$BAR") == 'foo bar', env.subst("$BAR")
1651 return 0
1653 a = SCons.Action.FunctionAction(f, {})
1654 a(target=1, source=2, env=Environment(BAR='foo bar',
1655 s=self))
1656 assert self.inc == 1, self.inc
1657 assert self.source == [2], self.source
1658 assert self.target == [1], self.target
1660 global count
1661 count = 0
1663 def function1(target, source, env) -> int:
1664 global count
1665 count = count + 1
1666 for t in target:
1667 with open(t, 'w') as f:
1668 f.write("function1\n")
1669 return 1
1671 act = SCons.Action.FunctionAction(function1, {})
1672 r = act(target=[outfile, outfile2], source=[], env=Environment())
1673 assert r.status == 1, r.status
1675 assert count == 1, count
1676 c = test.read(outfile, 'r')
1677 assert c == "function1\n", c
1678 c = test.read(outfile2, 'r')
1679 assert c == "function1\n", c
1681 class class1a:
1682 def __init__(self, target, source, env) -> None:
1683 with open(env['out'], 'w') as f:
1684 f.write("class1a\n")
1686 act = SCons.Action.FunctionAction(class1a, {})
1687 r = act([], [], Environment(out=outfile))
1688 assert isinstance(r.status, class1a), r.status
1689 c = test.read(outfile, 'r')
1690 assert c == "class1a\n", c
1692 class class1b:
1693 def __call__(self, target, source, env) -> int:
1694 with open(env['out'], 'w') as f:
1695 f.write("class1b\n")
1696 return 2
1698 act = SCons.Action.FunctionAction(class1b(), {})
1699 r = act([], [], Environment(out=outfile))
1700 assert r.status == 2, r.status
1701 c = test.read(outfile, 'r')
1702 assert c == "class1b\n", c
1704 def build_it(target, source, env, executor: Optional[ExecutorType] = None, self=self) -> int:
1705 self.build_it = 1
1706 return 0
1708 def string_it(target, source, env, executor: Optional[ExecutorType] = None, self=self):
1709 self.string_it = 1
1710 return None
1712 act = SCons.Action.FunctionAction(build_it,
1713 {'strfunction': string_it})
1714 r = act([], [], Environment())
1715 assert r == 0, r
1716 assert self.build_it
1717 assert self.string_it
1719 def test_get_contents(self) -> None:
1720 """Test fetching the contents of a function Action."""
1722 def LocalFunc() -> None:
1723 pass
1725 func_matches = {
1726 (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'),
1727 (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1728 (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1729 (3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1730 (3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1731 (3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1732 (3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
1733 (3, 12): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00y\x00),(),()'),
1734 (3, 13): bytearray(b'0, 0, 0, 0,(),(),(\x95\x00g\x00),(),()'),
1738 meth_matches = {
1739 (3, 5): bytearray(b'1, 1, 0, 0,(),(),(d\x00\x00S),(),()'),
1740 (3, 6): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1741 (3, 7): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1742 (3, 8): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1743 (3, 9): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1744 (3, 10): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1745 (3, 11): bytearray(b'1, 1, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
1746 (3, 12): bytearray(b'1, 1, 0, 0,(),(),(\x97\x00y\x00),(),()'),
1747 (3, 13): bytearray(b'1, 1, 0, 0,(),(),(\x95\x00g\x00),(),()'),
1750 def factory(act, **kw):
1751 return SCons.Action.FunctionAction(act, kw)
1753 with self.subTest():
1754 a = factory(GlobalFunc)
1755 c = a.get_contents(target=[], source=[], env=Environment())
1756 self.assertEqual(c, func_matches[sys.version_info[:2]])
1758 with self.subTest():
1759 a = factory(LocalFunc)
1760 c = a.get_contents(target=[], source=[], env=Environment())
1761 self.assertEqual(c, func_matches[sys.version_info[:2]])
1763 matches_foo = func_matches[sys.version_info[:2]] + b'foo'
1765 with self.subTest():
1766 a = factory(GlobalFunc, varlist=['XYZ'])
1767 c = a.get_contents(target=[], source=[], env=Environment())
1768 self.assertEqual(c, func_matches[sys.version_info[:2]])
1770 with self.subTest():
1771 c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo'))
1772 self.assertEqual(c, matches_foo)
1774 ##TODO: is this set of tests still needed?
1775 # Make sure a bare string varlist works
1776 with self.subTest():
1777 a = factory(GlobalFunc, varlist='XYZ')
1778 c = a.get_contents(target=[], source=[], env=Environment())
1779 self.assertEqual(c, func_matches[sys.version_info[:2]])
1781 with self.subTest():
1782 c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo'))
1783 self.assertIn(c, matches_foo)
1785 class Foo:
1786 def get_contents(self, target, source, env) -> bytes:
1787 return b'xyzzy'
1789 with self.subTest():
1790 a = factory(Foo())
1791 c = a.get_contents(target=[], source=[], env=Environment())
1792 self.assertEqual(c, b'xyzzy')
1794 class LocalClass:
1795 def LocalMethod(self) -> None:
1796 pass
1798 with self.subTest():
1799 lc = LocalClass()
1800 a = factory(lc.LocalMethod)
1801 c = a.get_contents(target=[], source=[], env=Environment())
1802 self.assertEqual(c, meth_matches[sys.version_info[:2]])
1804 def test_strfunction(self) -> None:
1805 """Test the FunctionAction.strfunction() method."""
1807 def func() -> None:
1808 pass
1810 def factory(act, **kw):
1811 return SCons.Action.FunctionAction(act, kw)
1813 a = factory(func)
1814 s = a.strfunction(target=[], source=[], env=Environment())
1815 assert s == 'func([], [])', s
1817 a = factory(func, strfunction=None)
1818 s = a.strfunction(target=[], source=[], env=Environment())
1819 assert s is None, s
1821 a = factory(func, cmdstr='function')
1822 s = a.strfunction(target=[], source=[], env=Environment())
1823 assert s == 'function', s
1826 class ListActionTestCase(unittest.TestCase):
1828 def test___init__(self) -> None:
1829 """Test creation of a list of subsidiary Actions."""
1831 def func() -> None:
1832 pass
1834 a = SCons.Action.ListAction(["x", func, ["y", "z"]])
1835 assert isinstance(a.list[0], SCons.Action.CommandAction)
1836 assert isinstance(a.list[1], SCons.Action.FunctionAction)
1837 assert isinstance(a.list[2], SCons.Action.ListAction)
1838 assert a.list[2].list[0].cmd_list == 'y'
1840 def test___str__(self) -> None:
1841 """Test the __str__() method for a list of subsidiary Actions."""
1843 def f(target, source, env) -> None:
1844 pass
1846 def g(target, source, env) -> None:
1847 pass
1849 a = SCons.Action.ListAction([f, g, "XXX", f])
1850 s = str(a)
1851 assert s == "f(target, source, env)\ng(target, source, env)\nXXX\nf(target, source, env)", s
1853 def test_genstring(self) -> None:
1854 """Test the genstring() method for a list of subsidiary Actions."""
1856 def f(target, source, env) -> None:
1857 pass
1859 def g(target, source, env, for_signature) -> str:
1860 return 'generated %s %s' % (target[0], source[0])
1862 g = SCons.Action.Action(g, generator=1)
1863 a = SCons.Action.ListAction([f, g, "XXX", f])
1864 s = a.genstring(['foo.x'], ['bar.y'], Environment())
1865 assert s == "f(target, source, env)\ngenerated foo.x bar.y\nXXX\nf(target, source, env)", s
1867 def test_execute(self) -> None:
1868 """Test executing a list of subsidiary Actions."""
1869 self.inc = 0
1871 def f(target, source, env) -> None:
1872 s = env['s']
1873 s.inc = s.inc + 1
1875 a = SCons.Action.ListAction([f, f, f])
1876 a([], [], Environment(s=self))
1877 assert self.inc == 3, self.inc
1879 cmd2 = r'%s %s %s syzygy' % (_python_, act_py, outfile)
1881 def function2(target, source, env) -> int:
1882 with open(env['out'], 'a') as f:
1883 f.write("function2\n")
1884 return 0
1886 class class2a:
1887 def __call__(self, target, source, env) -> int:
1888 with open(env['out'], 'a') as f:
1889 f.write("class2a\n")
1890 return 0
1892 class class2b:
1893 def __init__(self, target, source, env) -> None:
1894 with open(env['out'], 'a') as f:
1895 f.write("class2b\n")
1897 act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b])
1898 r = act([], [], Environment(out=outfile))
1899 assert isinstance(r.status, class2b), r.status
1900 c = test.read(outfile, 'r')
1901 assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
1903 def test_get_contents(self) -> None:
1904 """Test fetching the contents of a list of subsidiary Actions."""
1906 self.foo = 0
1908 def gen(target, source, env, for_signature) -> str:
1909 s = env['s']
1910 s.foo = 1
1911 return "y"
1913 a = SCons.Action.ListAction(["x",
1914 SCons.Action.Action(gen, generator=1),
1915 "z"])
1916 c = a.get_contents(target=[], source=[], env=Environment(s=self))
1917 assert self.foo == 1, self.foo
1918 assert c == b"xyz", c
1921 class LazyActionTestCase(unittest.TestCase):
1922 def test___init__(self) -> None:
1923 """Test creation of a lazy-evaluation Action."""
1925 # Environment variable references should create a special type
1926 # of LazyAction that lazily evaluates the variable for whether
1927 # it's a string or something else before doing anything.
1928 a9 = SCons.Action.Action('$FOO')
1929 assert isinstance(a9, SCons.Action.LazyAction), a9
1930 assert a9.var == 'FOO', a9.var
1932 a10 = SCons.Action.Action('${FOO}')
1933 assert isinstance(a10, SCons.Action.LazyAction), a10
1934 assert a10.var == 'FOO', a10.var
1936 def test_genstring(self) -> None:
1937 """Test the lazy-evaluation Action genstring() method."""
1939 def f(target, source, env) -> None:
1940 pass
1942 a = SCons.Action.Action('$BAR')
1943 env1 = Environment(BAR=f, s=self)
1944 env2 = Environment(BAR='xxx', s=self)
1945 s = a.genstring([], [], env=env1)
1946 assert s == "f(target, source, env)", s
1947 s = a.genstring([], [], env=env2)
1948 assert s == 'xxx', s
1950 def test_execute(self) -> None:
1951 """Test executing a lazy-evaluation Action."""
1953 def f(target, source, env) -> int:
1954 s = env['s']
1955 s.test = 1
1956 return 0
1958 a = SCons.Action.Action('$BAR')
1959 a([], [], env=Environment(BAR=f, s=self))
1960 assert self.test == 1, self.test
1961 cmd = r'%s %s %s lazy' % (_python_, act_py, outfile)
1962 a([], [], env=Environment(BAR=cmd, s=self))
1963 c = test.read(outfile, 'r')
1964 assert c == "act.py: 'lazy'\n", c
1966 def test_get_contents(self) -> None:
1967 """Test fetching the contents of a lazy-evaluation Action."""
1969 a = SCons.Action.Action("${FOO}")
1970 env = Environment(FOO=[["This", "is", "a", "test"]])
1971 c = a.get_contents(target=[], source=[], env=env)
1972 assert c == b"This is a test", c
1974 def test_get_contents_of_function_action(self) -> None:
1975 """Test fetching the contents of a lazy-evaluation FunctionAction."""
1977 def LocalFunc() -> None:
1978 pass
1980 func_matches = {
1981 (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'),
1982 (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1983 (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1984 (3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1985 (3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1986 (3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1987 (3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
1988 (3, 12): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00y\x00),(),()'),
1989 (3, 13): bytearray(b'0, 0, 0, 0,(),(),(\x95\x00g\x00),(),()'),
1992 meth_matches = [
1993 b"1, 1, 0, 0,(),(),(d\000\000S),(),()",
1994 b"1, 1, 0, 0,(),(),(d\x00\x00S),(),()",
1997 def factory(act, **kw):
1998 return SCons.Action.FunctionAction(act, kw)
2000 a = SCons.Action.Action("${FOO}")
2002 with self.subTest():
2003 env = Environment(FOO=factory(GlobalFunc))
2004 c = a.get_contents(target=[], source=[], env=env)
2005 self.assertEqual(c, func_matches[sys.version_info[:2]])
2007 with self.subTest():
2008 env = Environment(FOO=factory(LocalFunc))
2009 c = a.get_contents(target=[], source=[], env=env)
2010 self.assertEqual(c, func_matches[sys.version_info[:2]])
2012 # matches_foo = [x + b"foo" for x in func_matches]
2013 matches_foo = func_matches[sys.version_info[:2]] + b'foo'
2015 with self.subTest():
2016 env = Environment(FOO=factory(GlobalFunc, varlist=['XYZ']))
2017 c = a.get_contents(target=[], source=[], env=env)
2018 self.assertEqual(c, func_matches[sys.version_info[:2]])
2020 with self.subTest():
2021 env['XYZ'] = 'foo'
2022 c = a.get_contents(target=[], source=[], env=env)
2023 self.assertIn(c, matches_foo)
2026 class ActionCallerTestCase(unittest.TestCase):
2027 def test___init__(self) -> None:
2028 """Test creation of an ActionCaller"""
2030 ac = SCons.Action.ActionCaller(1, [2, 3], {'FOO': 4, 'BAR': 5})
2031 assert ac.parent == 1, ac.parent
2032 assert ac.args == [2, 3], ac.args
2033 assert ac.kw == {'FOO': 4, 'BAR': 5}, ac.kw
2035 def test_get_contents(self) -> None:
2036 """Test fetching the contents of an ActionCaller"""
2038 def strfunc() -> None:
2039 pass
2041 def LocalFunc() -> None:
2042 pass
2044 matches = {
2045 (3, 5): b'd\x00\x00S',
2046 (3, 6): b'd\x00S\x00',
2047 (3, 7): b'd\x00S\x00',
2048 (3, 8): b'd\x00S\x00',
2049 (3, 9): b'd\x00S\x00',
2050 (3, 10): b'd\x00S\x00',
2051 (3, 11): b'\x97\x00d\x00S\x00',
2052 (3, 12): b'\x97\x00y\x00',
2053 (3, 13): b'\x95\x00g\x00',
2056 with self.subTest():
2057 af = SCons.Action.ActionFactory(GlobalFunc, strfunc)
2058 ac = SCons.Action.ActionCaller(af, [], {})
2059 c = ac.get_contents([], [], Environment())
2060 self.assertEqual(c, matches[sys.version_info[:2]])
2062 with self.subTest():
2063 af = SCons.Action.ActionFactory(LocalFunc, strfunc)
2064 ac = SCons.Action.ActionCaller(af, [], {})
2065 c = ac.get_contents([], [], Environment())
2066 self.assertEqual(c, matches[sys.version_info[:2]])
2068 class LocalActFunc:
2069 def __call__(self) -> None:
2070 pass
2072 with self.subTest():
2073 af = SCons.Action.ActionFactory(GlobalActFunc(), strfunc)
2074 ac = SCons.Action.ActionCaller(af, [], {})
2075 c = ac.get_contents([], [], Environment())
2076 self.assertEqual(c, matches[sys.version_info[:2]])
2078 with self.subTest():
2079 af = SCons.Action.ActionFactory(LocalActFunc(), strfunc)
2080 ac = SCons.Action.ActionCaller(af, [], {})
2081 c = ac.get_contents([], [], Environment())
2082 self.assertEqual(c, matches[sys.version_info[:2]])
2084 matches = [
2085 b"<built-in function str>",
2086 b"<type 'str'>",
2089 with self.subTest():
2090 af = SCons.Action.ActionFactory(str, strfunc)
2091 ac = SCons.Action.ActionCaller(af, [], {})
2092 c = ac.get_contents([], [], Environment())
2093 self.assertIn(c, ("<built-in function str>", "<type 'str'>", "<class 'str'>"))
2094 # ^^ class str for python3
2096 def test___call__(self) -> None:
2097 """Test calling an ActionCaller"""
2099 actfunc_args = []
2101 def actfunc(a1, a2, a3, args=actfunc_args) -> None:
2102 args.extend([a1, a2, a3])
2104 def strfunc(a1, a2, a3) -> None:
2105 pass
2107 e = Environment(FOO=2, BAR=5)
2109 af = SCons.Action.ActionFactory(actfunc, strfunc)
2110 ac = SCons.Action.ActionCaller(af, ['$__env__', '$FOO', 3], {})
2111 ac([], [], e)
2112 assert actfunc_args[0] is e, actfunc_args
2113 assert actfunc_args[1] == '2', actfunc_args
2114 assert actfunc_args[2] == 3, actfunc_args
2115 del actfunc_args[:]
2117 ac = SCons.Action.ActionCaller(af, [], {'a3': '$__env__', 'a2': '$BAR', 'a1': 4})
2118 ac([], [], e)
2119 assert actfunc_args[0] == 4, actfunc_args
2120 assert actfunc_args[1] == '5', actfunc_args
2121 assert actfunc_args[2] is e, actfunc_args
2122 del actfunc_args[:]
2124 def test_strfunction(self) -> None:
2125 """Test calling the ActionCaller strfunction() method"""
2127 strfunc_args = []
2129 def actfunc(a1, a2, a3, a4) -> None:
2130 pass
2132 def strfunc(a1, a2, a3, a4, args=strfunc_args) -> None:
2133 args.extend([a1, a2, a3, a4])
2135 af = SCons.Action.ActionFactory(actfunc, strfunc)
2136 ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3, '$WS'], {})
2137 ac.strfunction([], [], Environment(FOO=2, WS='white space'))
2138 assert strfunc_args == [1, '2', 3, 'white space'], strfunc_args
2140 del strfunc_args[:]
2141 d = {'a3': 6, 'a2': '$BAR', 'a1': 4, 'a4': '$WS'}
2142 ac = SCons.Action.ActionCaller(af, [], d)
2143 ac.strfunction([], [], Environment(BAR=5, WS='w s'))
2144 assert strfunc_args == [4, '5', 6, 'w s'], strfunc_args
2147 class ActionFactoryTestCase(unittest.TestCase):
2148 def test___init__(self) -> None:
2149 """Test creation of an ActionFactory"""
2151 def actfunc() -> None:
2152 pass
2154 def strfunc() -> None:
2155 pass
2157 ac = SCons.Action.ActionFactory(actfunc, strfunc)
2158 assert ac.actfunc is actfunc, ac.actfunc
2159 assert ac.strfunc is strfunc, ac.strfunc
2161 def test___call__(self) -> None:
2162 """Test calling whatever's returned from an ActionFactory"""
2164 actfunc_args = []
2165 strfunc_args = []
2167 def actfunc(a1, a2, a3, args=actfunc_args) -> None:
2168 args.extend([a1, a2, a3])
2170 def strfunc(a1, a2, a3, args=strfunc_args) -> None:
2171 args.extend([a1, a2, a3])
2173 af = SCons.Action.ActionFactory(actfunc, strfunc)
2174 af(3, 6, 9)([], [], Environment())
2175 assert actfunc_args == [3, 6, 9], actfunc_args
2176 assert strfunc_args == [3, 6, 9], strfunc_args
2179 class ActionCompareTestCase(unittest.TestCase):
2180 def test_1_solo_name(self) -> None:
2181 """Test Lazy Cmd Generator Action get_name alone.
2183 Basically ensures we can locate the builder, comparing it to
2184 itself along the way."""
2186 bar = SCons.Builder.Builder(action={})
2187 env = Environment(BUILDERS={'BAR': bar})
2188 name = bar.get_name(env)
2189 assert name == 'BAR', name
2191 def test_2_multi_name(self) -> None:
2192 """Test LazyCmdGenerator Action get_name multi builders.
2194 Ensure that we can compare builders (and thereby actions) to
2195 each other safely."""
2197 foo = SCons.Builder.Builder(action='$FOO', suffix='.foo')
2198 bar = SCons.Builder.Builder(action={})
2199 assert foo != bar
2200 assert foo.action != bar.action
2201 env = Environment(BUILDERS={'FOO': foo, 'BAR': bar})
2202 name = foo.get_name(env)
2203 assert name == 'FOO', name
2204 name = bar.get_name(env)
2205 assert name == 'BAR', name
2207 def test_3_dict_names(self) -> None:
2208 """Test Action/Suffix dicts with get_name.
2210 Verifies that Action/Suffix dictionaries work correctly,
2211 especially two builders that can generate the same suffix,
2212 where one of the builders has a suffix dictionary with a None
2213 key."""
2215 foo = SCons.Builder.Builder(action='$FOO', suffix='.foo')
2216 bar = SCons.Builder.Builder(action={}, suffix={None: '.bar'})
2217 bar.add_action('.cow', "$MOO")
2218 dog = SCons.Builder.Builder(suffix='.bar')
2219 env = Environment(BUILDERS={'FOO': foo, 'BAR': bar, 'DOG': dog})
2221 assert foo.get_name(env) == 'FOO', foo.get_name(env)
2222 assert bar.get_name(env) == 'BAR', bar.get_name(env)
2223 assert dog.get_name(env) == 'DOG', dog.get_name(env)
2226 class TestClass:
2227 """A test class used by ObjectContentsTestCase.test_object_contents"""
2229 def __init__(self) -> None:
2230 self.a = "a"
2231 self.b = "b"
2233 def method(self, arg) -> None:
2234 pass
2237 class ObjectContentsTestCase(unittest.TestCase):
2238 def test_function_contents(self) -> None:
2239 """Test that Action._function_contents works"""
2241 def func1(a, b, c):
2242 """A test function"""
2243 return a
2245 # Since the python bytecode has per version differences,
2246 # we need different expected results per version
2247 # Note unlike the others, this result is a tuple, use assertIn
2248 expected = {
2249 (3, 5): (bytearray(b'3, 3, 0, 0,(),(),(|\x00\x00S),(),()'),),
2250 (3, 6): (bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),),
2251 (3, 7): (bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),),
2252 (3, 8): (bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),),
2253 (3, 9): (bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),),
2254 (3, 10): ( # 3.10.1, 3.10.2
2255 bytearray(b'3, 3, 0, 0,(N.),(),(|\x00S\x00),(),()'),
2256 bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'),
2258 (3, 11): (bytearray(b'3, 3, 0, 0,(),(),(\x97\x00|\x00S\x00),(),()'),),
2259 (3, 12): (bytearray(b'3, 3, 0, 0,(),(),(\x97\x00|\x00S\x00),(),()'),),
2260 (3, 13): (bytearray(b'3, 3, 0, 0,(),(),(\x95\x00U\x00$\x00),(),()'),),
2263 c = SCons.Action._function_contents(func1)
2264 self.assertIn(c, expected[sys.version_info[:2]])
2266 def test_object_contents(self) -> None:
2267 """Test that Action._object_contents works"""
2269 # See definition above
2270 o = TestClass()
2271 c = SCons.Action._object_contents(o)
2273 # c = SCons.Action._object_instance_content(o)
2275 # Since the python bytecode has per version differences,
2276 # we need different expected results per version
2277 expected = {
2278 (3, 5): bytearray(
2279 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}}}"
2281 (3, 6): bytearray(
2282 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}}}"
2284 (3, 7): bytearray(
2285 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}}}"
2287 (3, 8): bytearray(
2288 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}}}"
2290 (3, 9): bytearray(
2291 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}}}"
2293 (3, 10): bytearray(
2294 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}}}"
2296 (3, 11): bytearray(
2297 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}}}"
2299 (3, 12): bytearray(
2300 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}}}"
2302 (3, 13): bytearray(
2303 b"{TestClass:__main__}[[[(<class \'object\'>, ()), [(<class \'__main__.TestClass\'>, (<class \'object\'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(\x95\x00S\x01U\x00l\x00\x00\x00\x00\x00\x00\x00\x00\x00S\x02U\x00l\x01\x00\x00\x00\x00\x00\x00\x00\x00g\x00),(),(),2, 2, 0, 0,(),(),(\x95\x00g\x00),(),()}}{{{a=a,b=b}}}"
2307 self.assertEqual(c, expected[sys.version_info[:2]])
2309 def test_code_contents(self) -> None:
2310 """Test that Action._code_contents works"""
2312 code = compile("print('Hello, World!')", '<string>', 'exec')
2313 c = SCons.Action._code_contents(code)
2315 # Since the python bytecode has per version differences, we need different expected results per version
2316 expected = {
2317 (3, 5): bytearray(
2318 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00\x00d\x00\x00\x83\x01\x00\x01d\x01\x00S)'
2320 (3, 6): bytearray(
2321 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2323 (3, 7): bytearray(
2324 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2326 (3, 8): bytearray(
2327 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2329 (3, 9): bytearray(
2330 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2332 (3, 10): bytearray(
2333 b'0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'
2335 (3, 11): bytearray(
2336 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)'
2338 (3, 12): bytearray(
2339 b'0, 0, 0, 0,(Hello, World!),(print),(\x97\x00\x02\x00e\x00d\x00\xab\x01\x00\x00\x00\x00\x00\x00\x01\x00y\x01)'
2341 (3, 13): bytearray(
2342 b'0, 0, 0, 0,(Hello, World!),(print),(\x95\x00\\\x00"\x00S\x005\x01\x00\x00\x00\x00\x00\x00 \x00g\x01)'
2346 self.assertEqual(c, expected[sys.version_info[:2]])
2348 def test_uncaught_exception_bubbles(self):
2349 """Test that scons_subproc_run bubbles uncaught exceptions"""
2350 try:
2351 cp = scons_subproc_run(Environment(), None, stdout=PIPE)
2352 except EnvironmentError:
2353 pass
2354 except Exception:
2355 # pass the test
2356 return
2358 raise Exception("expected a non-EnvironmentError exception")
2361 def mock_subprocess_run(*args, **kwargs):
2362 """Replacement subprocess.run: return kwargs for checking."""
2363 kwargs.pop("env") # the value of env isn't interesting here
2364 return kwargs
2366 @mock.patch("subprocess.run", mock_subprocess_run)
2367 def test_scons_subproc_run(self):
2368 """Test the argument remapping options."""
2369 # set phony Python versions to trigger the logic in scons_subproc_run:
2370 # any version greater than 3.6, really
2371 save_info, sys.version_info = sys.version_info, (3, 11, 1)
2372 env = Environment()
2373 self.assertEqual(scons_subproc_run(env), {"check": False})
2374 with self.subTest():
2375 self.assertEqual(
2376 scons_subproc_run(env, error="raise"),
2377 {"check": True}
2379 with self.subTest():
2380 self.assertEqual(
2381 scons_subproc_run(env, capture_output=True),
2382 {"capture_output": True, "check": False},
2384 with self.subTest():
2385 self.assertEqual(
2386 scons_subproc_run(env, text=True),
2387 {"text": True, "check": False},
2390 # 3.6:
2391 sys.version_info = (3, 6, 2)
2392 with self.subTest():
2393 self.assertEqual(
2394 scons_subproc_run(env, capture_output=True),
2395 {"check": False, "stdout": PIPE, "stderr": PIPE},
2397 with self.subTest():
2398 self.assertEqual(
2399 scons_subproc_run(env, text=True),
2400 {"check": False, "universal_newlines": True},
2402 with self.subTest():
2403 self.assertEqual(
2404 scons_subproc_run(env, universal_newlines=True),
2405 {"universal_newlines": True, "check": False},
2407 sys.version_info = save_info
2410 if __name__ == "__main__":
2411 unittest.main()
2413 # Local Variables:
2414 # tab-width:4
2415 # indent-tabs-mode:nil
2416 # End:
2417 # vim: set expandtab tabstop=4 shiftwidth=4: