4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
30 import SCons.Taskmaster
41 def __init__(self, name, kids = [], scans = []):
49 self.prerequisites = []
51 def targets(self, node):
53 self.builder = Builder()
56 self.state = SCons.Node.no_state
59 self.waiting_parents = set()
60 self.waiting_s_e = set()
62 self.side_effects = []
64 self.postprocessed = None
67 self.always_build = None
69 def disambiguate(self):
72 def retrieve_from_cache(self):
75 cache_text.append(self.name + " retrieved")
86 built_text = self.name + " built"
90 built_text = built_text + " really"
92 def has_builder(self):
93 return not self.builder is None
96 return self.has_builder or self.side_effect
98 def alter_targets(self):
99 return self.alttargets, None
103 visited_nodes.append(self.name)
113 scan_called = scan_called + 1
114 self.kids = self.kids + self.scans
117 def scanner_key(self):
120 def add_to_waiting_parents(self, node):
121 wp = self.waiting_parents
130 def set_state(self, state):
133 def set_bsig(self, bsig):
136 def set_csig(self, csig):
139 def store_csig(self):
142 def store_bsig(self):
145 def is_pseudo_derived(self):
148 def is_up_to_date(self):
149 return self._current_val
151 def depends_on(self, nodes):
153 if node in self.kids:
160 def postprocess(self):
161 self.postprocessed = 1
162 self.waiting_parents = set()
164 def get_executor(self):
165 if not hasattr(self, 'executor'):
169 def get_action_targets(self):
171 def get_all_targets(self):
173 def get_all_children(self):
175 for node in self.targets:
176 result.extend(node.children())
178 def get_all_prerequisites(self):
180 def get_action_side_effects(self):
182 self.executor = Executor()
183 self.executor.targets = self.targets
186 class OtherError(Exception):
189 class MyException(Exception):
193 class TaskmasterTestCase(unittest.TestCase):
195 def test_next_task(self):
196 """Test fetching the next task
201 tm = SCons.Taskmaster.Taskmaster([n1, n1])
210 n3 = Node("n3", [n1, n2])
212 tm = SCons.Taskmaster.Taskmaster([n3])
217 assert built_text == "n1 built", built_text
224 assert built_text == "n2 built", built_text
231 assert built_text == "n3 built", built_text
235 assert tm.next_task() == None
237 built_text = "up to date: "
240 class MyTask(SCons.Taskmaster.Task):
243 if self.targets[0].get_state() == SCons.Node.up_to_date:
245 built_text = self.targets[0].name + " up-to-date top"
247 built_text = self.targets[0].name + " up-to-date"
249 self.targets[0].build()
251 n1.set_state(SCons.Node.no_state)
253 n2.set_state(SCons.Node.no_state)
255 n3.set_state(SCons.Node.no_state)
257 tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
262 assert built_text == "n1 up-to-date", built_text
269 assert built_text == "n2 up-to-date", built_text
276 assert built_text == "n3 up-to-date top", built_text
280 assert tm.next_task() == None
285 n3 = Node("n3", [n1, n2])
287 n5 = Node("n5", [n3, n4])
288 tm = SCons.Taskmaster.Taskmaster([n5])
291 assert t1.get_target() == n1
294 assert t2.get_target() == n2
297 assert t4.get_target() == n4
306 assert t3.get_target() == n3
311 assert t5.get_target() == n5, t5.get_target()
315 assert tm.next_task() == None
319 n4.set_state(SCons.Node.executed)
320 tm = SCons.Taskmaster.Taskmaster([n4])
321 assert tm.next_task() == None
324 n2 = Node("n2", [n1])
325 tm = SCons.Taskmaster.Taskmaster([n2,n2])
330 assert tm.next_task() == None
335 n3 = Node("n3", [n1], [n2])
336 tm = SCons.Taskmaster.Taskmaster([n3])
338 target = t.get_target()
339 assert target == n1, target
343 target = t.get_target()
344 assert target == n2, target
348 target = t.get_target()
349 assert target == n3, target
352 assert tm.next_task() == None
356 n3 = Node("n3", [n1, n2])
357 n4 = Node("n4", [n3])
358 n5 = Node("n5", [n3])
361 tm = SCons.Taskmaster.Taskmaster([n4])
363 assert t.get_target() == n1
367 assert t.get_target() == n2
371 assert t.get_target() == n3
375 assert t.get_target() == n4
378 assert tm.next_task() == None
379 assert scan_called == 4, scan_called
381 tm = SCons.Taskmaster.Taskmaster([n5])
383 assert t.get_target() == n5, t.get_target()
385 assert tm.next_task() == None
386 assert scan_called == 5, scan_called
391 n4 = Node("n4", [n1,n2,n3])
392 n5 = Node("n5", [n4])
394 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
395 tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
397 assert t.get_target() == n1
398 assert n4.state == SCons.Node.executing, n4.state
402 assert t.get_target() == n2
406 assert t.get_target() == n3
410 assert t.get_target() == n4
414 assert t.get_target() == n5
415 assert not tm.next_task()
422 n4 = Node("n4", [n1,n2,n3])
423 def reverse(dependencies):
424 dependencies.reverse()
426 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
428 assert t.get_target() == n3, t.get_target()
432 assert t.get_target() == n2, t.get_target()
436 assert t.get_target() == n1, t.get_target()
440 assert t.get_target() == n4, t.get_target()
449 tm = SCons.Taskmaster.Taskmaster([n5])
451 assert t.get_target() == n5
455 tm = SCons.Taskmaster.Taskmaster([n6])
457 assert t.get_target() == n7
461 assert t.get_target() == n6
466 n2 = Node("n2", [n1])
467 n1.set_state(SCons.Node.failed)
468 tm = SCons.Taskmaster.Taskmaster([n2])
469 assert tm.next_task() is None
473 n1.targets = [n1, n2]
475 tm = SCons.Taskmaster.Taskmaster([n1])
481 assert s == SCons.Node.executed, s
483 assert s == SCons.Node.executed, s
486 def test_make_ready_out_of_date(self):
487 """Test the Task.make_ready() method's list of out-of-date Nodes
490 def TaskGen(tm, targets, top, node, ood=ood):
491 class MyTask(SCons.Taskmaster.Task):
492 def make_ready(self):
493 SCons.Taskmaster.Task.make_ready(self)
494 self.ood.extend(self.out_of_date)
495 t = MyTask(tm, targets, top, node)
508 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4, a5],
513 assert ood == [n1], ood
517 assert ood == [], ood
521 assert ood == [n3], ood
525 assert ood == [], ood
529 assert ood == [a5], ood
531 def test_make_ready_exception(self):
532 """Test handling exceptions from Task.make_ready()
534 class MyTask(SCons.Taskmaster.Task):
535 def make_ready(self):
536 raise MyException, "from make_ready()"
539 tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
541 exc_type, exc_value, exc_tb = t.exception
542 assert exc_type == MyException, repr(exc_type)
543 assert str(exc_value) == "from make_ready()", exc_value
546 def test_make_ready_all(self):
547 """Test the make_ready_all() method"""
548 class MyTask(SCons.Taskmaster.Task):
549 make_ready = SCons.Taskmaster.Task.make_ready_all
558 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
561 target = t.get_target()
562 assert target is n1, target
563 assert target.state == SCons.Node.executing, target.state
565 target = t.get_target()
566 assert target is c2, target
567 assert target.state == SCons.Node.up_to_date, target.state
569 target = t.get_target()
570 assert target is n3, target
571 assert target.state == SCons.Node.executing, target.state
573 target = t.get_target()
574 assert target is c4, target
575 assert target.state == SCons.Node.up_to_date, target.state
584 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
588 target = t.get_target()
589 assert target is n1, target
590 assert target.state == SCons.Node.executing, target.state
592 target = t.get_target()
593 assert target is c2, target
594 assert target.state == SCons.Node.executing, target.state
596 target = t.get_target()
597 assert target is n3, target
598 assert target.state == SCons.Node.executing, target.state
600 target = t.get_target()
601 assert target is c4, target
602 assert target.state == SCons.Node.executing, target.state
607 def test_children_errors(self):
608 """Test errors when fetching the children of a node.
610 class StopNode(Node):
612 raise SCons.Errors.StopError, "stop!"
613 class ExitNode(Node):
618 tm = SCons.Taskmaster.Taskmaster([n1])
620 exc_type, exc_value, exc_tb = t.exception
621 assert exc_type == SCons.Errors.StopError, repr(exc_type)
622 assert str(exc_value) == "stop!", exc_value
625 tm = SCons.Taskmaster.Taskmaster([n2])
627 exc_type, exc_value = t.exception
628 assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
629 assert exc_value.node == n2, exc_value.node
630 assert exc_value.status == 77, exc_value.status
632 def test_cycle_detection(self):
633 """Test detecting dependency cycles
636 n2 = Node("n2", [n1])
637 n3 = Node("n3", [n2])
640 tm = SCons.Taskmaster.Taskmaster([n3])
643 except SCons.Errors.UserError, e:
644 assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
646 assert 'Did not catch expected UserError'
648 def test_next_top_level_candidate(self):
649 """Test the next_top_level_candidate() method
652 n2 = Node("n2", [n1])
653 n3 = Node("n3", [n2])
655 tm = SCons.Taskmaster.Taskmaster([n3])
657 assert t.targets == [n1], t.targets
659 assert t.targets == [n3], map(str, t.targets)
660 assert t.top == 1, t.top
663 """Test the stop() method
665 Both default and overridden in a subclass.
671 n3 = Node("n3", [n1, n2])
673 tm = SCons.Taskmaster.Taskmaster([n3])
677 assert built_text == "n1 built", built_text
680 assert built_text == "n1 built really", built_text
683 assert tm.next_task() is None
685 class MyTM(SCons.Taskmaster.Taskmaster):
688 built_text = "MyTM.stop()"
689 SCons.Taskmaster.Taskmaster.stop(self)
693 n3 = Node("n3", [n1, n2])
697 tm.next_task().execute()
698 assert built_text == "n1 built"
701 assert built_text == "MyTM.stop()"
702 assert tm.next_task() is None
704 def test_executed(self):
705 """Test when a task has been executed
711 tm = SCons.Taskmaster.Taskmaster([n1])
715 n1.set_state(SCons.Node.executing)
720 assert s == SCons.Node.executed, s
721 assert built_text == "xxx really", built_text
722 assert visited_nodes == ['n1'], visited_nodes
725 tm = SCons.Taskmaster.Taskmaster([n2])
727 built_text = "should_not_change"
735 assert built_text == "should_not_change", built_text
736 assert visited_nodes == ['n2'], visited_nodes
740 n3.targets = [n3, n4]
741 tm = SCons.Taskmaster.Taskmaster([n3])
744 n3.set_state(SCons.Node.up_to_date)
745 n4.set_state(SCons.Node.executing)
750 assert s == SCons.Node.up_to_date, s
752 assert s == SCons.Node.executed, s
753 assert visited_nodes == ['n3', 'n4'], visited_nodes
755 def test_prepare(self):
756 """Test preparation of multiple Nodes for a task
760 tm = SCons.Taskmaster.Taskmaster([n1, n2])
762 # This next line is moderately bogus. We're just reaching
763 # in and setting the targets for this task to an array. The
764 # "right" way to do this would be to have the next_task() call
765 # set it up by having something that approximates a real Builder
766 # return this list--but that's more work than is probably
767 # warranted right now.
768 n1.get_executor().targets = [n1, n2]
775 tm = SCons.Taskmaster.Taskmaster([n3, n4])
777 # More bogus reaching in and setting the targets.
778 n3.set_state(SCons.Node.up_to_date)
779 n3.get_executor().targets = [n3, n4]
784 # If the Node has had an exception recorded while it was getting
785 # prepared, then prepare() should raise that exception.
786 class MyException(Exception):
791 tm = SCons.Taskmaster.Taskmaster([n5])
793 t.exception_set((MyException, "exception value"))
797 except MyException, e:
801 assert exc_caught, "did not catch expected MyException"
802 assert str(e) == "exception value", e
803 assert built_text is None, built_text
805 # Regression test, make sure we prepare not only
806 # all targets, but their side effects as well.
813 n6.side_effects = [ n8 ]
814 n7.side_effects = [ n9, n10 ]
816 tm = SCons.Taskmaster.Taskmaster([n6, n7])
818 # More bogus reaching in and setting the targets.
819 n6.get_executor().targets = [n6, n7]
827 # Make sure we call an Executor's prepare() method.
828 class ExceptionExecutor:
830 raise Exception, "Executor.prepare() exception"
831 def get_all_targets(self):
833 def get_all_children(self):
835 for node in self.nodes:
836 result.extend(node.children())
838 def get_all_prerequisites(self):
840 def get_action_side_effects(self):
844 n11.executor = ExceptionExecutor()
845 n11.executor.nodes = [n11]
846 tm = SCons.Taskmaster.Taskmaster([n11])
851 assert str(e) == "Executor.prepare() exception", e
853 raise AssertionError, "did not catch expected exception"
855 def test_execute(self):
856 """Test executing a task
862 tm = SCons.Taskmaster.Taskmaster([n1])
865 assert built_text == "n1 built", built_text
867 def raise_UserError():
868 raise SCons.Errors.UserError
870 n2.build = raise_UserError
871 tm = SCons.Taskmaster.Taskmaster([n2])
875 except SCons.Errors.UserError:
878 raise TestFailed, "did not catch expected UserError"
880 def raise_BuildError():
881 raise SCons.Errors.BuildError
883 n3.build = raise_BuildError
884 tm = SCons.Taskmaster.Taskmaster([n3])
888 except SCons.Errors.BuildError:
891 raise TestFailed, "did not catch expected BuildError"
893 # On a generic (non-BuildError) exception from a Builder,
894 # the target should throw a BuildError exception with the
895 # args set to the exception value, instance, and traceback.
896 def raise_OtherError():
899 n4.build = raise_OtherError
900 tm = SCons.Taskmaster.Taskmaster([n4])
904 except SCons.Errors.BuildError, e:
905 assert e.node == n4, e.node
906 assert e.errstr == "OtherError : ", e.errstr
907 assert len(e.exc_info) == 3, e.exc_info
908 exc_traceback = sys.exc_info()[2]
909 assert type(e.exc_info[2]) == type(exc_traceback), e.exc_info[2]
911 raise TestFailed, "did not catch expected BuildError"
918 tm = SCons.Taskmaster.Taskmaster([n5])
920 # This next line is moderately bogus. We're just reaching
921 # in and setting the targets for this task to an array. The
922 # "right" way to do this would be to have the next_task() call
923 # set it up by having something that approximates a real Builder
924 # return this list--but that's more work than is probably
925 # warranted right now.
928 assert built_text == "n5 built", built_text
929 assert cache_text == [], cache_text
937 tm = SCons.Taskmaster.Taskmaster([n7])
939 # This next line is moderately bogus. We're just reaching
940 # in and setting the targets for this task to an array. The
941 # "right" way to do this would be to have the next_task() call
942 # set it up by having something that approximates a real Builder
943 # return this list--but that's more work than is probably
944 # warranted right now.
947 assert built_text is None, built_text
948 assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
950 def test_exception(self):
951 """Test generic Taskmaster exception handling
955 tm = SCons.Taskmaster.Taskmaster([n1])
958 t.exception_set((1, 2))
959 exc_type, exc_value = t.exception
960 assert exc_type == 1, exc_type
961 assert exc_value == 2, exc_value
964 assert t.exception == 3
968 t.exception_set(None)
969 exc_type, exc_value, exc_tb = t.exception
970 assert exc_type is ZeroDivisionError, exc_type
972 "integer division or modulo",
973 "integer division or modulo by zero",
975 assert str(exc_value) in exception_values, exc_value
977 class Exception1(Exception):
980 t.exception_set((Exception1, None))
984 exc_type, exc_value = sys.exc_info()[:2]
985 assert exc_type == Exception1, exc_type
986 assert str(exc_value) == '', exc_value
988 assert 0, "did not catch expected exception"
990 class Exception2(Exception):
993 t.exception_set((Exception2, "xyzzy"))
997 exc_type, exc_value = sys.exc_info()[:2]
998 assert exc_type == Exception2, exc_type
999 assert str(exc_value) == "xyzzy", exc_value
1001 assert 0, "did not catch expected exception"
1003 class Exception3(Exception):
1009 tb = sys.exc_info()[2]
1010 t.exception_set((Exception3, "arg", tb))
1014 exc_type, exc_value, exc_tb = sys.exc_info()
1015 assert exc_type == Exception3, exc_type
1016 assert str(exc_value) == "arg", exc_value
1018 x = traceback.extract_tb(tb)[-1]
1019 y = traceback.extract_tb(exc_tb)[-1]
1020 assert x == y, "x = %s, y = %s" % (x, y)
1022 assert 0, "did not catch expected exception"
1024 def test_postprocess(self):
1025 """Test postprocessing targets to give them a chance to clean up
1028 tm = SCons.Taskmaster.Taskmaster([n1])
1031 assert not n1.postprocessed
1033 assert n1.postprocessed
1037 tm = SCons.Taskmaster.Taskmaster([n2, n3])
1039 assert not n2.postprocessed
1040 assert not n3.postprocessed
1043 assert n2.postprocessed
1044 assert not n3.postprocessed
1047 assert n2.postprocessed
1048 assert n3.postprocessed
1050 def test_trace(self):
1051 """Test Taskmaster tracing
1055 trace = StringIO.StringIO()
1058 n3 = Node("n3", [n1, n2])
1059 tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1064 n1.set_state(SCons.Node.executed)
1069 n2.set_state(SCons.Node.executed)
1077 value = trace.getvalue()
1080 Taskmaster: Looking for a node to evaluate
1081 Taskmaster: Considering node <no_state 0 'n1'> and its children:
1082 Taskmaster: Evaluating <pending 0 'n1'>
1084 Task.make_ready_current(): node <pending 0 'n1'>
1085 Task.prepare(): node <executing 0 'n1'>
1086 Task.execute(): node <executing 0 'n1'>
1087 Task.postprocess(): node <executing 0 'n1'>
1089 Taskmaster: Looking for a node to evaluate
1090 Taskmaster: Considering node <executed 0 'n1'> and its children:
1091 Taskmaster: already handled (executed)
1092 Taskmaster: Considering node <no_state 0 'n3'> and its children:
1093 Taskmaster: <executed 0 'n1'>
1094 Taskmaster: <no_state 0 'n2'>
1095 Taskmaster: adjusted ref count: <pending 1 'n3'>, child 'n2'
1096 Taskmaster: Considering node <no_state 0 'n2'> and its children:
1097 Taskmaster: Evaluating <pending 0 'n2'>
1099 Task.make_ready_current(): node <pending 0 'n2'>
1100 Task.prepare(): node <executing 0 'n2'>
1101 Task.execute(): node <executing 0 'n2'>
1102 Task.postprocess(): node <executing 0 'n2'>
1103 Task.postprocess(): removing <executing 0 'n2'>
1104 Task.postprocess(): adjusted parent ref count <pending 0 'n3'>
1106 Taskmaster: Looking for a node to evaluate
1107 Taskmaster: Considering node <pending 0 'n3'> and its children:
1108 Taskmaster: <executed 0 'n1'>
1109 Taskmaster: <executed 0 'n2'>
1110 Taskmaster: Evaluating <pending 0 'n3'>
1112 Task.make_ready_current(): node <pending 0 'n3'>
1113 Task.prepare(): node <executing 0 'n3'>
1114 Task.execute(): node <executing 0 'n3'>
1115 Task.postprocess(): node <executing 0 'n3'>
1117 Taskmaster: Looking for a node to evaluate
1118 Taskmaster: No candidate anymore.
1121 assert value == expect, value
1125 if __name__ == "__main__":
1126 suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1127 if not unittest.TextTestRunner().run(suite).wasSuccessful():