Followon to PR #4348: more bool fixes
[scons.git] / SCons / Taskmaster / TaskmasterTests.py
blob0dd91f12b5ec044ed40ebb9e6f50d2717f750504
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.
24 import SCons.compat
26 import sys
27 import unittest
29 import SCons.Taskmaster
30 import SCons.Errors
32 import TestCommon
34 built_text = None
35 cache_text = []
36 visited_nodes = []
37 executed = None
38 scan_called = 0
41 class Node:
42 def __init__(self, name, kids=[], scans=[]) -> None:
43 self.name = name
44 self.kids = kids
45 self.scans = scans
46 self.cached = False
47 self.scanned = False
48 self.scanner = None
49 self.targets = [self]
50 self.prerequisites = None
52 class Builder:
53 def targets(self, node):
54 return node.targets
56 self.builder = Builder()
57 self.bsig = None
58 self.csig = None
59 self.state = SCons.Node.no_state
60 self.prepared = None
61 self.ref_count = 0
62 self.waiting_parents = set()
63 self.waiting_s_e = set()
64 self.side_effect = False
65 self.side_effects = []
66 self.alttargets = []
67 self.postprocessed = None
68 self._bsig_val = None
69 self._current_val = 0
70 self.always_build = None
72 def disambiguate(self):
73 return self
75 def push_to_cache(self) -> None:
76 pass
78 def retrieve_from_cache(self) -> bool:
79 global cache_text
80 if self.cached:
81 cache_text.append(self.name + " retrieved")
82 return self.cached
84 def make_ready(self) -> None:
85 pass
87 def prepare(self) -> None:
88 self.prepared = 1
89 self.get_binfo()
91 def build(self) -> None:
92 global built_text
93 built_text = self.name + " built"
95 def remove(self) -> None:
96 pass
98 # The following four methods new_binfo(), del_binfo(),
99 # get_binfo(), clear() as well as its calls have been added
100 # to support the cached_execute() test (issue #2720).
101 # They are full copies (or snippets) of their actual
102 # counterparts in the Node class...
103 def new_binfo(self):
104 binfo = "binfo"
105 return binfo
107 def del_binfo(self) -> None:
108 """Delete the build info from this node."""
109 try:
110 delattr(self, 'binfo')
111 except AttributeError:
112 pass
114 def get_binfo(self):
115 """Fetch a node's build information."""
116 try:
117 return self.binfo
118 except AttributeError:
119 pass
121 binfo = self.new_binfo()
122 self.binfo = binfo
124 return binfo
126 def clear(self) -> None:
127 # The del_binfo() call here isn't necessary for normal execution,
128 # but is for interactive mode, where we might rebuild the same
129 # target and need to start from scratch.
130 self.del_binfo()
132 def built(self) -> None:
133 global built_text
134 if not self.cached:
135 built_text = built_text + " really"
137 # Clear the implicit dependency caches of any Nodes
138 # waiting for this Node to be built.
139 for parent in self.waiting_parents:
140 parent.implicit = None
142 self.clear()
144 def release_target_info(self) -> None:
145 pass
147 def has_builder(self) -> bool:
148 return self.builder is not None
150 def is_derived(self) -> bool:
151 return self.has_builder or self.side_effect
153 def alter_targets(self):
154 return self.alttargets, None
156 def visited(self) -> None:
157 global visited_nodes
158 visited_nodes.append(self.name)
160 def children(self):
161 if not self.scanned:
162 self.scan()
163 self.scanned = True
164 return self.kids
166 def scan(self) -> None:
167 global scan_called
168 scan_called = scan_called + 1
169 self.kids = self.kids + self.scans
170 self.scans = []
172 def scanner_key(self):
173 return self.name
175 def add_to_waiting_parents(self, node) -> int:
176 wp = self.waiting_parents
177 if node in wp:
178 return 0
179 wp.add(node)
180 return 1
182 def get_state(self):
183 return self.state
185 def set_state(self, state) -> None:
186 self.state = state
188 def set_bsig(self, bsig) -> None:
189 self.bsig = bsig
191 def set_csig(self, csig) -> None:
192 self.csig = csig
194 def store_csig(self) -> None:
195 pass
197 def store_bsig(self) -> None:
198 pass
200 def is_up_to_date(self) -> bool:
201 return self._current_val
203 def __str__(self) -> str:
204 return self.name
206 def postprocess(self) -> None:
207 self.postprocessed = 1
208 self.waiting_parents = set()
210 def get_executor(self):
211 if not hasattr(self, 'executor'):
212 class Executor:
213 def prepare(self) -> None:
214 pass
216 def get_action_targets(self):
217 return self.targets
219 def get_all_targets(self):
220 return self.targets
222 def get_all_children(self):
223 result = []
224 for node in self.targets:
225 result.extend(node.children())
226 return result
228 def get_all_prerequisites(self):
229 return []
231 def get_action_side_effects(self):
232 return []
234 self.executor = Executor()
235 self.executor.targets = self.targets
236 return self.executor
238 def get_internal_path(self):
240 Should only be used (currently) by TaskmasterTestCase.test_cached_execute_target_unlink_fails
242 return str(self)
245 class OtherError(Exception):
246 pass
249 class MyException(Exception):
250 pass
253 class TaskmasterTestCase(unittest.TestCase):
255 def test_next_task(self) -> None:
256 """Test fetching the next task
258 global built_text
260 n1 = Node("n1")
261 tm = SCons.Taskmaster.Taskmaster([n1, n1])
262 t = tm.next_task()
263 t.prepare()
264 t.execute()
265 t = tm.next_task()
266 assert t is None
268 n1 = Node("n1")
269 n2 = Node("n2")
270 n3 = Node("n3", [n1, n2])
272 tm = SCons.Taskmaster.Taskmaster([n3])
274 t = tm.next_task()
275 t.prepare()
276 t.execute()
277 assert built_text == "n1 built", built_text
278 t.executed()
279 t.postprocess()
281 t = tm.next_task()
282 t.prepare()
283 t.execute()
284 assert built_text == "n2 built", built_text
285 t.executed()
286 t.postprocess()
288 t = tm.next_task()
289 t.prepare()
290 t.execute()
291 assert built_text == "n3 built", built_text
292 t.executed()
293 t.postprocess()
295 assert tm.next_task() is None
297 built_text = "up to date: "
298 top_node = n3
300 class MyTask(SCons.Taskmaster.AlwaysTask):
301 def execute(self) -> None:
302 global built_text
303 if self.targets[0].get_state() == SCons.Node.up_to_date:
304 if self.top:
305 built_text = self.targets[0].name + " up-to-date top"
306 else:
307 built_text = self.targets[0].name + " up-to-date"
308 else:
309 self.targets[0].build()
311 n1.set_state(SCons.Node.no_state)
312 n1._current_val = 1
313 n2.set_state(SCons.Node.no_state)
314 n2._current_val = 1
315 n3.set_state(SCons.Node.no_state)
316 n3._current_val = 1
317 tm = SCons.Taskmaster.Taskmaster(targets=[n3], tasker=MyTask)
319 t = tm.next_task()
320 t.prepare()
321 t.execute()
322 assert built_text == "n1 up-to-date", built_text
323 t.executed()
324 t.postprocess()
326 t = tm.next_task()
327 t.prepare()
328 t.execute()
329 assert built_text == "n2 up-to-date", built_text
330 t.executed()
331 t.postprocess()
333 t = tm.next_task()
334 t.prepare()
335 t.execute()
336 assert built_text == "n3 up-to-date top", built_text
337 t.executed()
338 t.postprocess()
340 assert tm.next_task() is None
342 n1 = Node("n1")
343 n2 = Node("n2")
344 n3 = Node("n3", [n1, n2])
345 n4 = Node("n4")
346 n5 = Node("n5", [n3, n4])
347 tm = SCons.Taskmaster.Taskmaster([n5])
349 t1 = tm.next_task()
350 assert t1.get_target() == n1
352 t2 = tm.next_task()
353 assert t2.get_target() == n2
355 t4 = tm.next_task()
356 assert t4.get_target() == n4
357 t4.executed()
358 t4.postprocess()
360 t1.executed()
361 t1.postprocess()
362 t2.executed()
363 t2.postprocess()
364 t3 = tm.next_task()
365 assert t3.get_target() == n3
367 t3.executed()
368 t3.postprocess()
369 t5 = tm.next_task()
370 assert t5.get_target() == n5, t5.get_target()
371 t5.executed()
372 t5.postprocess()
374 assert tm.next_task() is None
376 n4 = Node("n4")
377 n4.set_state(SCons.Node.executed)
378 tm = SCons.Taskmaster.Taskmaster([n4])
379 assert tm.next_task() is None
381 n1 = Node("n1")
382 n2 = Node("n2", [n1])
383 tm = SCons.Taskmaster.Taskmaster([n2, n2])
384 t = tm.next_task()
385 t.executed()
386 t.postprocess()
387 t = tm.next_task()
388 assert tm.next_task() is None
390 n1 = Node("n1")
391 n2 = Node("n2")
392 n3 = Node("n3", [n1], [n2])
393 tm = SCons.Taskmaster.Taskmaster([n3])
394 t = tm.next_task()
395 target = t.get_target()
396 assert target == n1, target
397 t.executed()
398 t.postprocess()
399 t = tm.next_task()
400 target = t.get_target()
401 assert target == n2, target
402 t.executed()
403 t.postprocess()
404 t = tm.next_task()
405 target = t.get_target()
406 assert target == n3, target
407 t.executed()
408 t.postprocess()
409 assert tm.next_task() is None
411 n1 = Node("n1")
412 n2 = Node("n2")
413 n3 = Node("n3", [n1, n2])
414 n4 = Node("n4", [n3])
415 n5 = Node("n5", [n3])
416 global scan_called
417 scan_called = 0
418 tm = SCons.Taskmaster.Taskmaster([n4])
419 t = tm.next_task()
420 assert t.get_target() == n1
421 t.executed()
422 t.postprocess()
423 t = tm.next_task()
424 assert t.get_target() == n2
425 t.executed()
426 t.postprocess()
427 t = tm.next_task()
428 assert t.get_target() == n3
429 t.executed()
430 t.postprocess()
431 t = tm.next_task()
432 assert t.get_target() == n4
433 t.executed()
434 t.postprocess()
435 assert tm.next_task() is None
436 assert scan_called == 4, scan_called
438 tm = SCons.Taskmaster.Taskmaster([n5])
439 t = tm.next_task()
440 assert t.get_target() == n5, t.get_target()
441 t.executed()
442 assert tm.next_task() is None
443 assert scan_called == 5, scan_called
445 n1 = Node("n1")
446 n2 = Node("n2")
447 n3 = Node("n3")
448 n4 = Node("n4", [n1, n2, n3])
449 n5 = Node("n5", [n4])
450 n3.side_effect = True
451 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
452 tm = SCons.Taskmaster.Taskmaster([n1, n2, n3, n4, n5])
453 t = tm.next_task()
454 assert t.get_target() == n1
455 assert n4.state == SCons.Node.executing, n4.state
456 t.executed()
457 t.postprocess()
458 t = tm.next_task()
459 assert t.get_target() == n2
460 t.executed()
461 t.postprocess()
462 t = tm.next_task()
463 assert t.get_target() == n3
464 t.executed()
465 t.postprocess()
466 t = tm.next_task()
467 assert t.get_target() == n4
468 t.executed()
469 t.postprocess()
470 t = tm.next_task()
471 assert t.get_target() == n5
472 assert not tm.next_task()
473 t.executed()
474 t.postprocess()
476 n1 = Node("n1")
477 n2 = Node("n2")
478 n3 = Node("n3")
479 n4 = Node("n4", [n1, n2, n3])
481 def reverse(dependencies):
482 dependencies.reverse()
483 return dependencies
485 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
486 t = tm.next_task()
487 assert t.get_target() == n3, t.get_target()
488 t.executed()
489 t.postprocess()
490 t = tm.next_task()
491 assert t.get_target() == n2, t.get_target()
492 t.executed()
493 t.postprocess()
494 t = tm.next_task()
495 assert t.get_target() == n1, t.get_target()
496 t.executed()
497 t.postprocess()
498 t = tm.next_task()
499 assert t.get_target() == n4, t.get_target()
500 t.executed()
501 t.postprocess()
503 n5 = Node("n5")
504 n6 = Node("n6")
505 n7 = Node("n7")
506 n6.alttargets = [n7]
508 tm = SCons.Taskmaster.Taskmaster([n5])
509 t = tm.next_task()
510 assert t.get_target() == n5
511 t.executed()
512 t.postprocess()
514 tm = SCons.Taskmaster.Taskmaster([n6])
515 t = tm.next_task()
516 assert t.get_target() == n7
517 t.executed()
518 t.postprocess()
519 t = tm.next_task()
520 assert t.get_target() == n6
521 t.executed()
522 t.postprocess()
524 n1 = Node("n1")
525 n2 = Node("n2", [n1])
526 n1.set_state(SCons.Node.failed)
527 tm = SCons.Taskmaster.Taskmaster([n2])
528 assert tm.next_task() is None
530 n1 = Node("n1")
531 n2 = Node("n2")
532 n1.targets = [n1, n2]
533 n1._current_val = 1
534 tm = SCons.Taskmaster.Taskmaster([n1])
535 t = tm.next_task()
536 t.executed()
537 t.postprocess()
539 s = n1.get_state()
540 assert s == SCons.Node.executed, s
541 s = n2.get_state()
542 assert s == SCons.Node.executed, s
544 def test_make_ready_out_of_date(self) -> None:
545 """Test the Task.make_ready() method's list of out-of-date Nodes
547 ood = []
549 def TaskGen(tm, targets, top, node, ood=ood):
550 class MyTask(SCons.Taskmaster.AlwaysTask):
551 def make_ready(self) -> None:
552 SCons.Taskmaster.Task.make_ready(self)
553 self.ood.extend(self.out_of_date)
555 t = MyTask(tm, targets, top, node)
556 t.ood = ood
557 return t
559 n1 = Node("n1")
560 c2 = Node("c2")
561 c2._current_val = 1
562 n3 = Node("n3")
563 c4 = Node("c4")
564 c4._current_val = 1
565 a5 = Node("a5")
566 a5._current_val = 1
567 a5.always_build = 1
568 tm = SCons.Taskmaster.Taskmaster(targets=[n1, c2, n3, c4, a5],
569 tasker=TaskGen)
571 del ood[:]
572 t = tm.next_task()
573 assert ood == [n1], ood
575 del ood[:]
576 t = tm.next_task()
577 assert ood == [], ood
579 del ood[:]
580 t = tm.next_task()
581 assert ood == [n3], ood
583 del ood[:]
584 t = tm.next_task()
585 assert ood == [], ood
587 del ood[:]
588 t = tm.next_task()
589 assert ood == [a5], ood
591 def test_make_ready_exception(self) -> None:
592 """Test handling exceptions from Task.make_ready()
595 class MyTask(SCons.Taskmaster.AlwaysTask):
596 def make_ready(self):
597 raise MyException("from make_ready()")
599 n1 = Node("n1")
600 tm = SCons.Taskmaster.Taskmaster(targets=[n1], tasker=MyTask)
601 t = tm.next_task()
602 exc_type, exc_value, exc_tb = t.exception
603 assert exc_type == MyException, repr(exc_type)
604 assert str(exc_value) == "from make_ready()", exc_value
606 def test_needs_execute(self) -> None:
607 """Test that we can't instantiate a Task subclass without needs_execute
609 We should be getting:
610 TypeError: Can't instantiate abstract class MyTask with abstract methods needs_execute
613 class MyTask(SCons.Taskmaster.Task):
614 pass
616 n1 = Node("n1")
617 tm = SCons.Taskmaster.Taskmaster(targets=[n1], tasker=MyTask)
618 with self.assertRaises(TypeError):
619 _ = tm.next_task()
621 def test_make_ready_all(self) -> None:
622 """Test the make_ready_all() method"""
624 class MyTask(SCons.Taskmaster.AlwaysTask):
625 make_ready = SCons.Taskmaster.Task.make_ready_all
627 n1 = Node("n1")
628 c2 = Node("c2")
629 c2._current_val = 1
630 n3 = Node("n3")
631 c4 = Node("c4")
632 c4._current_val = 1
634 tm = SCons.Taskmaster.Taskmaster(targets=[n1, c2, n3, c4])
636 t = tm.next_task()
637 target = t.get_target()
638 assert target is n1, target
639 assert target.state == SCons.Node.executing, target.state
640 t = tm.next_task()
641 target = t.get_target()
642 assert target is c2, target
643 assert target.state == SCons.Node.up_to_date, target.state
644 t = tm.next_task()
645 target = t.get_target()
646 assert target is n3, target
647 assert target.state == SCons.Node.executing, target.state
648 t = tm.next_task()
649 target = t.get_target()
650 assert target is c4, target
651 assert target.state == SCons.Node.up_to_date, target.state
652 t = tm.next_task()
653 assert t is None
655 n1 = Node("n1")
656 c2 = Node("c2")
657 n3 = Node("n3")
658 c4 = Node("c4")
660 tm = SCons.Taskmaster.Taskmaster(targets=[n1, c2, n3, c4],
661 tasker=MyTask)
663 t = tm.next_task()
664 target = t.get_target()
665 assert target is n1, target
666 assert target.state == SCons.Node.executing, target.state
667 t = tm.next_task()
668 target = t.get_target()
669 assert target is c2, target
670 assert target.state == SCons.Node.executing, target.state
671 t = tm.next_task()
672 target = t.get_target()
673 assert target is n3, target
674 assert target.state == SCons.Node.executing, target.state
675 t = tm.next_task()
676 target = t.get_target()
677 assert target is c4, target
678 assert target.state == SCons.Node.executing, target.state
679 t = tm.next_task()
680 assert t is None
682 def test_children_errors(self) -> None:
683 """Test errors when fetching the children of a node.
686 class StopNode(Node):
687 def children(self):
688 raise SCons.Errors.StopError("stop!")
690 class ExitNode(Node):
691 def children(self) -> None:
692 sys.exit(77)
694 n1 = StopNode("n1")
695 tm = SCons.Taskmaster.Taskmaster([n1])
696 t = tm.next_task()
697 exc_type, exc_value, exc_tb = t.exception
698 assert exc_type == SCons.Errors.StopError, repr(exc_type)
699 assert str(exc_value) == "stop!", exc_value
701 n2 = ExitNode("n2")
702 tm = SCons.Taskmaster.Taskmaster([n2])
703 t = tm.next_task()
704 exc_type, exc_value = t.exception
705 assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
706 assert exc_value.node == n2, exc_value.node
707 assert exc_value.status == 77, exc_value.status
709 def test_cycle_detection(self) -> None:
710 """Test detecting dependency cycles
712 n1 = Node("n1")
713 n2 = Node("n2", [n1])
714 n3 = Node("n3", [n2])
715 n1.kids = [n3]
717 tm = SCons.Taskmaster.Taskmaster([n3])
718 try:
719 t = tm.next_task()
720 except SCons.Errors.UserError as e:
721 assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
722 else:
723 assert 'Did not catch expected UserError'
725 def test_next_top_level_candidate(self) -> None:
726 """Test the next_top_level_candidate() method
728 n1 = Node("n1")
729 n2 = Node("n2", [n1])
730 n3 = Node("n3", [n2])
732 tm = SCons.Taskmaster.Taskmaster([n3])
733 t = tm.next_task()
734 assert t.targets == [n1], t.targets
735 t.fail_stop()
736 assert t.targets == [n3], list(map(str, t.targets))
737 assert t.top == 1, t.top
739 def test_stop(self) -> None:
740 """Test the stop() method
742 Both default and overridden in a subclass.
744 global built_text
746 n1 = Node("n1")
747 n2 = Node("n2")
748 n3 = Node("n3", [n1, n2])
750 tm = SCons.Taskmaster.Taskmaster([n3])
751 t = tm.next_task()
752 t.prepare()
753 t.execute()
754 assert built_text == "n1 built", built_text
755 t.executed()
756 t.postprocess()
757 assert built_text == "n1 built really", built_text
759 tm.stop()
760 assert tm.next_task() is None
762 class MyTM(SCons.Taskmaster.Taskmaster):
763 def stop(self) -> None:
764 global built_text
765 built_text = "MyTM.stop()"
766 SCons.Taskmaster.Taskmaster.stop(self)
768 n1 = Node("n1")
769 n2 = Node("n2")
770 n3 = Node("n3", [n1, n2])
772 built_text = None
773 tm = MyTM([n3])
774 tm.next_task().execute()
775 assert built_text == "n1 built"
777 tm.stop()
778 assert built_text == "MyTM.stop()"
779 assert tm.next_task() is None
781 def test_executed(self) -> None:
782 """Test when a task has been executed
784 global built_text
785 global visited_nodes
787 n1 = Node("n1")
788 tm = SCons.Taskmaster.Taskmaster([n1])
789 t = tm.next_task()
790 built_text = "xxx"
791 visited_nodes = []
792 n1.set_state(SCons.Node.executing)
794 t.executed()
796 s = n1.get_state()
797 assert s == SCons.Node.executed, s
798 assert built_text == "xxx really", built_text
799 assert visited_nodes == ['n1'], visited_nodes
801 n2 = Node("n2")
802 tm = SCons.Taskmaster.Taskmaster([n2])
803 t = tm.next_task()
804 built_text = "should_not_change"
805 visited_nodes = []
806 n2.set_state(None)
808 t.executed()
810 s = n2.get_state()
811 assert s is None, s
812 assert built_text == "should_not_change", built_text
813 assert visited_nodes == ['n2'], visited_nodes
815 n3 = Node("n3")
816 n4 = Node("n4")
817 n3.targets = [n3, n4]
818 tm = SCons.Taskmaster.Taskmaster([n3])
819 t = tm.next_task()
820 visited_nodes = []
821 n3.set_state(SCons.Node.up_to_date)
822 n4.set_state(SCons.Node.executing)
824 t.executed()
826 s = n3.get_state()
827 assert s == SCons.Node.up_to_date, s
828 s = n4.get_state()
829 assert s == SCons.Node.executed, s
830 assert visited_nodes == ['n3', 'n4'], visited_nodes
832 def test_prepare(self):
833 """Test preparation of multiple Nodes for a task
835 n1 = Node("n1")
836 n2 = Node("n2")
837 tm = SCons.Taskmaster.Taskmaster([n1, n2])
838 t = tm.next_task()
839 # This next line is moderately bogus. We're just reaching
840 # in and setting the targets for this task to an array. The
841 # "right" way to do this would be to have the next_task() call
842 # set it up by having something that approximates a real Builder
843 # return this list--but that's more work than is probably
844 # warranted right now.
845 n1.get_executor().targets = [n1, n2]
846 t.prepare()
847 assert n1.prepared
848 assert n2.prepared
850 n3 = Node("n3")
851 n4 = Node("n4")
852 tm = SCons.Taskmaster.Taskmaster([n3, n4])
853 t = tm.next_task()
854 # More bogus reaching in and setting the targets.
855 n3.set_state(SCons.Node.up_to_date)
856 n3.get_executor().targets = [n3, n4]
857 t.prepare()
858 assert n3.prepared
859 assert n4.prepared
861 # If the Node has had an exception recorded while it was getting
862 # prepared, then prepare() should raise that exception.
863 class MyException(Exception):
864 pass
866 built_text = None
867 n5 = Node("n5")
868 tm = SCons.Taskmaster.Taskmaster([n5])
869 t = tm.next_task()
870 t.exception_set((MyException, "exception value"))
871 exc_caught = None
872 exc_actually_caught = None
873 exc_value = None
874 try:
875 t.prepare()
876 except MyException as e:
877 exc_caught = 1
878 exc_value = e
879 except Exception as exc_actually_caught:
880 pass
881 assert exc_caught, "did not catch expected MyException: %s" % exc_actually_caught
882 assert str(exc_value) == "exception value", exc_value
883 assert built_text is None, built_text
885 # Regression test, make sure we prepare not only
886 # all targets, but their side effects as well.
887 n6 = Node("n6")
888 n7 = Node("n7")
889 n8 = Node("n8")
890 n9 = Node("n9")
891 n10 = Node("n10")
893 n6.side_effects = [n8]
894 n7.side_effects = [n9, n10]
896 tm = SCons.Taskmaster.Taskmaster([n6, n7])
897 t = tm.next_task()
898 # More bogus reaching in and setting the targets.
899 n6.get_executor().targets = [n6, n7]
900 t.prepare()
901 assert n6.prepared
902 assert n7.prepared
903 assert n8.prepared
904 assert n9.prepared
905 assert n10.prepared
907 # Make sure we call an Executor's prepare() method.
908 class ExceptionExecutor:
909 def prepare(self):
910 raise Exception("Executor.prepare() exception")
912 def get_all_targets(self):
913 return self.nodes
915 def get_all_children(self):
916 result = []
917 for node in self.nodes:
918 result.extend(node.children())
919 return result
921 def get_all_prerequisites(self):
922 return []
924 def get_action_side_effects(self):
925 return []
927 n11 = Node("n11")
928 n11.executor = ExceptionExecutor()
929 n11.executor.nodes = [n11]
930 tm = SCons.Taskmaster.Taskmaster([n11])
931 t = tm.next_task()
932 try:
933 t.prepare()
934 except Exception as e:
935 assert str(e) == "Executor.prepare() exception", e
936 else:
937 raise AssertionError("did not catch expected exception")
939 def test_execute(self) -> None:
940 """Test executing a task
942 global built_text
943 global cache_text
945 n1 = Node("n1")
946 tm = SCons.Taskmaster.Taskmaster([n1])
947 t = tm.next_task()
948 t.execute()
949 assert built_text == "n1 built", built_text
951 def raise_UserError():
952 raise SCons.Errors.UserError
954 n2 = Node("n2")
955 n2.build = raise_UserError
956 tm = SCons.Taskmaster.Taskmaster([n2])
957 t = tm.next_task()
958 try:
959 t.execute()
960 except SCons.Errors.UserError:
961 pass
962 else:
963 self.fail("did not catch expected UserError")
965 def raise_BuildError():
966 raise SCons.Errors.BuildError
968 n3 = Node("n3")
969 n3.build = raise_BuildError
970 tm = SCons.Taskmaster.Taskmaster([n3])
971 t = tm.next_task()
972 try:
973 t.execute()
974 except SCons.Errors.BuildError:
975 pass
976 else:
977 self.fail("did not catch expected BuildError")
979 # On a generic (non-BuildError) exception from a Builder,
980 # the target should throw a BuildError exception with the
981 # args set to the exception value, instance, and traceback.
982 def raise_OtherError():
983 raise OtherError
985 n4 = Node("n4")
986 n4.build = raise_OtherError
987 tm = SCons.Taskmaster.Taskmaster([n4])
988 t = tm.next_task()
989 try:
990 t.execute()
991 except SCons.Errors.BuildError as e:
992 assert e.node == n4, e.node
993 assert e.errstr == "OtherError : ", e.errstr
994 assert len(e.exc_info) == 3, e.exc_info
995 exc_traceback = sys.exc_info()[2]
996 assert isinstance(e.exc_info[2], type(exc_traceback)), e.exc_info[2]
997 else:
998 self.fail("did not catch expected BuildError")
1000 built_text = None
1001 cache_text = []
1002 n5 = Node("n5")
1003 n6 = Node("n6")
1004 n6.cached = True
1005 tm = SCons.Taskmaster.Taskmaster([n5])
1006 t = tm.next_task()
1007 # This next line is moderately bogus. We're just reaching
1008 # in and setting the targets for this task to an array. The
1009 # "right" way to do this would be to have the next_task() call
1010 # set it up by having something that approximates a real Builder
1011 # return this list--but that's more work than is probably
1012 # warranted right now.
1013 t.targets = [n5, n6]
1014 t.execute()
1015 assert built_text == "n5 built", built_text
1016 assert cache_text == [], cache_text
1018 built_text = None
1019 cache_text = []
1020 n7 = Node("n7")
1021 n8 = Node("n8")
1022 n7.cached = True
1023 n8.cached = True
1024 tm = SCons.Taskmaster.Taskmaster([n7])
1025 t = tm.next_task()
1026 # This next line is moderately bogus. We're just reaching
1027 # in and setting the targets for this task to an array. The
1028 # "right" way to do this would be to have the next_task() call
1029 # set it up by having something that approximates a real Builder
1030 # return this list--but that's more work than is probably
1031 # warranted right now.
1032 t.targets = [n7, n8]
1033 t.execute()
1034 assert built_text is None, built_text
1035 assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
1037 def test_cached_execute(self) -> None:
1038 """Test executing a task with cached targets
1040 # In issue #2720 Alexei Klimkin detected that the previous
1041 # workflow for execute() led to problems in a multithreaded build.
1042 # We have:
1043 # task.prepare()
1044 # task.execute()
1045 # task.executed()
1046 # -> node.visited()
1047 # for the Serial flow, but
1048 # - Parallel - - Worker -
1049 # task.prepare()
1050 # requestQueue.put(task)
1051 # task = requestQueue.get()
1052 # task.execute()
1053 # resultQueue.put(task)
1054 # task = resultQueue.get()
1055 # task.executed()
1056 # ->node.visited()
1057 # in parallel. Since execute() used to call built() when a target
1058 # was cached, it could unblock dependent nodes before the binfo got
1059 # restored again in visited(). This resulted in spurious
1060 # "file not found" build errors, because files fetched from cache would
1061 # be seen as not up to date and wouldn't be scanned for implicit
1062 # dependencies.
1064 # The following test ensures that execute() only marks targets as cached,
1065 # but the actual call to built() happens in executed() only.
1066 # Like this, the binfo should still be intact after calling execute()...
1067 global cache_text
1069 n1 = Node("n1")
1070 # Mark the node as being cached
1071 n1.cached = True
1072 tm = SCons.Taskmaster.Taskmaster([n1])
1073 t = tm.next_task()
1074 t.prepare()
1075 t.execute()
1076 assert cache_text == ["n1 retrieved"], cache_text
1077 # If no binfo exists anymore, something has gone wrong...
1078 has_binfo = hasattr(n1, 'binfo')
1079 assert has_binfo, has_binfo
1081 def test_cached_execute_target_unlink_fails(self) -> None:
1082 """Test executing a task with cached targets where unlinking one of the targets fail
1084 global cache_text
1085 import SCons.Warnings
1087 cache_text = []
1088 n1 = Node("n1")
1089 n2 = Node("not-cached")
1091 class DummyFS:
1092 def unlink(self, _):
1093 raise IOError
1095 n1.fs = DummyFS()
1097 # Mark the node as being cached
1098 n1.cached = True
1099 # Add n2 as a target for n1
1100 n1.targets.append(n2)
1101 # Explicitly mark n2 as not cached
1102 n2.cached = False
1104 # Save SCons.Warnings.warn so we can mock it and catch it being called for unlink failures
1105 _save_warn = SCons.Warnings.warn
1106 issued_warnings = []
1108 def fake_warnings_warn(clz, message) -> None:
1109 nonlocal issued_warnings
1110 issued_warnings.append((clz, message))
1111 SCons.Warnings.warn = fake_warnings_warn
1113 tm = SCons.Taskmaster.Taskmaster([n1, n2])
1114 t = tm.next_task()
1115 t.prepare()
1116 t.execute()
1118 # Restore saved warn
1119 SCons.Warnings.warn = _save_warn
1121 self.assertTrue(len(issued_warnings) == 1,
1122 msg='More than expected warnings (1) were issued %d' % len(issued_warnings))
1123 self.assertEqual(issued_warnings[0][0], SCons.Warnings.CacheCleanupErrorWarning,
1124 msg='Incorrect warning class')
1125 self.assertEqual(issued_warnings[0][1],
1126 'Failed copying all target files from cache, Error while attempting to remove file n1 retrieved from cache: ')
1127 self.assertEqual(cache_text, ["n1 retrieved"], msg=cache_text)
1130 def test_exception(self) -> None:
1131 """Test generic Taskmaster exception handling
1134 n1 = Node("n1")
1135 tm = SCons.Taskmaster.Taskmaster([n1])
1136 t = tm.next_task()
1138 t.exception_set((1, 2))
1139 exc_type, exc_value = t.exception
1140 assert exc_type == 1, exc_type
1141 assert exc_value == 2, exc_value
1143 t.exception_set(3)
1144 assert t.exception == 3
1146 try:
1147 1 // 0
1148 except:
1149 # Moved from below
1150 t.exception_set(None)
1151 # pass
1153 # Having this here works for python 2.x,
1154 # but it is a tuple (None, None, None) when called outside
1155 # an except statement
1156 # t.exception_set(None)
1158 exc_type, exc_value, exc_tb = t.exception
1159 assert exc_type is ZeroDivisionError, "Expecting ZeroDevisionError got:%s" % exc_type
1160 exception_values = [
1161 "integer division or modulo",
1162 "integer division or modulo by zero",
1163 "integer division by zero", # PyPy2
1165 assert str(exc_value) in exception_values, exc_value
1167 class Exception1(Exception):
1168 pass
1170 # Previously value was None, but while PY2 None = "", in Py3 None != "", so set to ""
1171 t.exception_set((Exception1, ""))
1172 try:
1173 t.exception_raise()
1174 except:
1175 exc_type, exc_value = sys.exc_info()[:2]
1176 assert exc_type == Exception1, exc_type
1177 assert str(exc_value) == '', "Expecting empty string got:%s (type %s)" % (exc_value, type(exc_value))
1178 else:
1179 assert 0, "did not catch expected exception"
1181 class Exception2(Exception):
1182 pass
1184 t.exception_set((Exception2, "xyzzy"))
1185 try:
1186 t.exception_raise()
1187 except:
1188 exc_type, exc_value = sys.exc_info()[:2]
1189 assert exc_type == Exception2, exc_type
1190 assert str(exc_value) == "xyzzy", exc_value
1191 else:
1192 assert 0, "did not catch expected exception"
1194 class Exception3(Exception):
1195 pass
1197 try:
1198 1 // 0
1199 except:
1200 tb = sys.exc_info()[2]
1201 t.exception_set((Exception3, "arg", tb))
1202 try:
1203 t.exception_raise()
1204 except:
1205 exc_type, exc_value, exc_tb = sys.exc_info()
1206 assert exc_type == Exception3, exc_type
1207 assert str(exc_value) == "arg", exc_value
1208 import traceback
1209 x = traceback.extract_tb(tb)[-1]
1210 y = traceback.extract_tb(exc_tb)[-1]
1211 assert x == y, "x = %s, y = %s" % (x, y)
1212 else:
1213 assert 0, "did not catch expected exception"
1215 def test_postprocess(self) -> None:
1216 """Test postprocessing targets to give them a chance to clean up
1218 n1 = Node("n1")
1219 tm = SCons.Taskmaster.Taskmaster([n1])
1221 t = tm.next_task()
1222 assert not n1.postprocessed
1223 t.postprocess()
1224 assert n1.postprocessed
1226 n2 = Node("n2")
1227 n3 = Node("n3")
1228 tm = SCons.Taskmaster.Taskmaster([n2, n3])
1230 assert not n2.postprocessed
1231 assert not n3.postprocessed
1232 t = tm.next_task()
1233 t.postprocess()
1234 assert n2.postprocessed
1235 assert not n3.postprocessed
1236 t = tm.next_task()
1237 t.postprocess()
1238 assert n2.postprocessed
1239 assert n3.postprocessed
1241 def test_trace(self) -> None:
1242 """Test Taskmaster tracing
1244 import io
1246 trace = io.StringIO()
1247 n1 = Node("n1")
1248 n2 = Node("n2")
1249 n3 = Node("n3", [n1, n2])
1250 tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1251 t = tm.next_task()
1252 t.prepare()
1253 t.execute()
1254 t.postprocess()
1255 n1.set_state(SCons.Node.executed)
1256 t = tm.next_task()
1257 t.prepare()
1258 t.execute()
1259 t.postprocess()
1260 n2.set_state(SCons.Node.executed)
1261 t = tm.next_task()
1262 t.prepare()
1263 t.execute()
1264 t.postprocess()
1265 t = tm.next_task()
1266 assert t is None
1268 value = trace.getvalue()
1269 expect = """\
1271 Taskmaster: Looking for a node to evaluate
1272 Taskmaster: Considering node <no_state 0 'n1'> and its children:
1273 Taskmaster: Evaluating <pending 0 'n1'>
1275 Task.make_ready_current(): node <pending 0 'n1'>
1276 Task.prepare(): node <executing 0 'n1'>
1277 Task.execute(): node <executing 0 'n1'>
1278 Task.postprocess(): node <executing 0 'n1'>
1280 Taskmaster: Looking for a node to evaluate
1281 Taskmaster: Considering node <executed 0 'n1'> and its children:
1282 Taskmaster: already handled (executed)
1283 Taskmaster: Considering node <no_state 0 'n3'> and its children:
1284 Taskmaster: <executed 0 'n1'>
1285 Taskmaster: <no_state 0 'n2'>
1286 Taskmaster: adjusted ref count: <pending 1 'n3'>, child 'n2'
1287 Taskmaster: Considering node <no_state 0 'n2'> and its children:
1288 Taskmaster: Evaluating <pending 0 'n2'>
1290 Task.make_ready_current(): node <pending 0 'n2'>
1291 Task.prepare(): node <executing 0 'n2'>
1292 Task.execute(): node <executing 0 'n2'>
1293 Task.postprocess(): node <executing 0 'n2'>
1294 Task.postprocess(): removing <executing 0 'n2'>
1295 Task.postprocess(): adjusted parent ref count <pending 0 'n3'>
1297 Taskmaster: Looking for a node to evaluate
1298 Taskmaster: Considering node <pending 0 'n3'> and its children:
1299 Taskmaster: <executed 0 'n1'>
1300 Taskmaster: <executed 0 'n2'>
1301 Taskmaster: Evaluating <pending 0 'n3'>
1303 Task.make_ready_current(): node <pending 0 'n3'>
1304 Task.prepare(): node <executing 0 'n3'>
1305 Task.execute(): node <executing 0 'n3'>
1306 Task.postprocess(): node <executing 0 'n3'>
1308 Taskmaster: Looking for a node to evaluate
1309 Taskmaster: No candidate anymore.
1312 if value != expect:
1313 TestCommon.TestCommon.detailed_diff(value, expect)
1315 assert value == expect, "Expected taskmaster trace contents didn't match. See above"
1318 if __name__ == "__main__":
1319 unittest.main()
1321 # Local Variables:
1322 # tab-width:4
1323 # indent-tabs-mode:nil
1324 # End:
1325 # vim: set expandtab tabstop=4 shiftwidth=4: