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 push_to_cache(self):
75 def retrieve_from_cache(self):
78 cache_text.append(self.name + " retrieved")
89 built_text = self.name + " built"
94 built_text = built_text + " really"
96 def has_builder(self):
97 return not self.builder is None
100 return self.has_builder or self.side_effect
102 def alter_targets(self):
103 return self.alttargets, None
107 visited_nodes.append(self.name)
117 scan_called = scan_called + 1
118 self.kids = self.kids + self.scans
121 def scanner_key(self):
124 def add_to_waiting_parents(self, node):
125 wp = self.waiting_parents
134 def set_state(self, state):
137 def set_bsig(self, bsig):
140 def set_csig(self, csig):
143 def store_csig(self):
146 def store_bsig(self):
149 def is_pseudo_derived(self):
152 def is_up_to_date(self):
153 return self._current_val
155 def depends_on(self, nodes):
157 if node in self.kids:
164 def postprocess(self):
165 self.postprocessed = 1
166 self.waiting_parents = set()
168 def get_executor(self):
169 if not hasattr(self, 'executor'):
173 def get_action_targets(self):
175 def get_all_targets(self):
177 def get_all_children(self):
179 for node in self.targets:
180 result.extend(node.children())
182 def get_all_prerequisites(self):
184 def get_action_side_effects(self):
186 self.executor = Executor()
187 self.executor.targets = self.targets
190 class OtherError(Exception):
193 class MyException(Exception):
197 class TaskmasterTestCase(unittest.TestCase):
199 def test_next_task(self):
200 """Test fetching the next task
205 tm = SCons.Taskmaster.Taskmaster([n1, n1])
214 n3 = Node("n3", [n1, n2])
216 tm = SCons.Taskmaster.Taskmaster([n3])
221 assert built_text == "n1 built", built_text
228 assert built_text == "n2 built", built_text
235 assert built_text == "n3 built", built_text
239 assert tm.next_task() is None
241 built_text = "up to date: "
244 class MyTask(SCons.Taskmaster.Task):
247 if self.targets[0].get_state() == SCons.Node.up_to_date:
249 built_text = self.targets[0].name + " up-to-date top"
251 built_text = self.targets[0].name + " up-to-date"
253 self.targets[0].build()
255 n1.set_state(SCons.Node.no_state)
257 n2.set_state(SCons.Node.no_state)
259 n3.set_state(SCons.Node.no_state)
261 tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
266 assert built_text == "n1 up-to-date", built_text
273 assert built_text == "n2 up-to-date", built_text
280 assert built_text == "n3 up-to-date top", built_text
284 assert tm.next_task() is None
289 n3 = Node("n3", [n1, n2])
291 n5 = Node("n5", [n3, n4])
292 tm = SCons.Taskmaster.Taskmaster([n5])
295 assert t1.get_target() == n1
298 assert t2.get_target() == n2
301 assert t4.get_target() == n4
310 assert t3.get_target() == n3
315 assert t5.get_target() == n5, t5.get_target()
319 assert tm.next_task() is None
323 n4.set_state(SCons.Node.executed)
324 tm = SCons.Taskmaster.Taskmaster([n4])
325 assert tm.next_task() is None
328 n2 = Node("n2", [n1])
329 tm = SCons.Taskmaster.Taskmaster([n2,n2])
334 assert tm.next_task() is None
339 n3 = Node("n3", [n1], [n2])
340 tm = SCons.Taskmaster.Taskmaster([n3])
342 target = t.get_target()
343 assert target == n1, target
347 target = t.get_target()
348 assert target == n2, target
352 target = t.get_target()
353 assert target == n3, target
356 assert tm.next_task() is None
360 n3 = Node("n3", [n1, n2])
361 n4 = Node("n4", [n3])
362 n5 = Node("n5", [n3])
365 tm = SCons.Taskmaster.Taskmaster([n4])
367 assert t.get_target() == n1
371 assert t.get_target() == n2
375 assert t.get_target() == n3
379 assert t.get_target() == n4
382 assert tm.next_task() is None
383 assert scan_called == 4, scan_called
385 tm = SCons.Taskmaster.Taskmaster([n5])
387 assert t.get_target() == n5, t.get_target()
389 assert tm.next_task() is None
390 assert scan_called == 5, scan_called
395 n4 = Node("n4", [n1,n2,n3])
396 n5 = Node("n5", [n4])
398 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
399 tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
401 assert t.get_target() == n1
402 assert n4.state == SCons.Node.executing, n4.state
406 assert t.get_target() == n2
410 assert t.get_target() == n3
414 assert t.get_target() == n4
418 assert t.get_target() == n5
419 assert not tm.next_task()
426 n4 = Node("n4", [n1,n2,n3])
427 def reverse(dependencies):
428 dependencies.reverse()
430 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
432 assert t.get_target() == n3, t.get_target()
436 assert t.get_target() == n2, t.get_target()
440 assert t.get_target() == n1, t.get_target()
444 assert t.get_target() == n4, t.get_target()
453 tm = SCons.Taskmaster.Taskmaster([n5])
455 assert t.get_target() == n5
459 tm = SCons.Taskmaster.Taskmaster([n6])
461 assert t.get_target() == n7
465 assert t.get_target() == n6
470 n2 = Node("n2", [n1])
471 n1.set_state(SCons.Node.failed)
472 tm = SCons.Taskmaster.Taskmaster([n2])
473 assert tm.next_task() is None
477 n1.targets = [n1, n2]
479 tm = SCons.Taskmaster.Taskmaster([n1])
485 assert s == SCons.Node.executed, s
487 assert s == SCons.Node.executed, s
490 def test_make_ready_out_of_date(self):
491 """Test the Task.make_ready() method's list of out-of-date Nodes
494 def TaskGen(tm, targets, top, node, ood=ood):
495 class MyTask(SCons.Taskmaster.Task):
496 def make_ready(self):
497 SCons.Taskmaster.Task.make_ready(self)
498 self.ood.extend(self.out_of_date)
499 t = MyTask(tm, targets, top, node)
512 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4, a5],
517 assert ood == [n1], ood
521 assert ood == [], ood
525 assert ood == [n3], ood
529 assert ood == [], ood
533 assert ood == [a5], ood
535 def test_make_ready_exception(self):
536 """Test handling exceptions from Task.make_ready()
538 class MyTask(SCons.Taskmaster.Task):
539 def make_ready(self):
540 raise MyException, "from make_ready()"
543 tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
545 exc_type, exc_value, exc_tb = t.exception
546 assert exc_type == MyException, repr(exc_type)
547 assert str(exc_value) == "from make_ready()", exc_value
550 def test_make_ready_all(self):
551 """Test the make_ready_all() method"""
552 class MyTask(SCons.Taskmaster.Task):
553 make_ready = SCons.Taskmaster.Task.make_ready_all
562 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
565 target = t.get_target()
566 assert target is n1, target
567 assert target.state == SCons.Node.executing, target.state
569 target = t.get_target()
570 assert target is c2, target
571 assert target.state == SCons.Node.up_to_date, target.state
573 target = t.get_target()
574 assert target is n3, target
575 assert target.state == SCons.Node.executing, target.state
577 target = t.get_target()
578 assert target is c4, target
579 assert target.state == SCons.Node.up_to_date, target.state
588 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
592 target = t.get_target()
593 assert target is n1, target
594 assert target.state == SCons.Node.executing, target.state
596 target = t.get_target()
597 assert target is c2, target
598 assert target.state == SCons.Node.executing, target.state
600 target = t.get_target()
601 assert target is n3, target
602 assert target.state == SCons.Node.executing, target.state
604 target = t.get_target()
605 assert target is c4, target
606 assert target.state == SCons.Node.executing, target.state
611 def test_children_errors(self):
612 """Test errors when fetching the children of a node.
614 class StopNode(Node):
616 raise SCons.Errors.StopError, "stop!"
617 class ExitNode(Node):
622 tm = SCons.Taskmaster.Taskmaster([n1])
624 exc_type, exc_value, exc_tb = t.exception
625 assert exc_type == SCons.Errors.StopError, repr(exc_type)
626 assert str(exc_value) == "stop!", exc_value
629 tm = SCons.Taskmaster.Taskmaster([n2])
631 exc_type, exc_value = t.exception
632 assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
633 assert exc_value.node == n2, exc_value.node
634 assert exc_value.status == 77, exc_value.status
636 def test_cycle_detection(self):
637 """Test detecting dependency cycles
640 n2 = Node("n2", [n1])
641 n3 = Node("n3", [n2])
644 tm = SCons.Taskmaster.Taskmaster([n3])
647 except SCons.Errors.UserError, e:
648 assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
650 assert 'Did not catch expected UserError'
652 def test_next_top_level_candidate(self):
653 """Test the next_top_level_candidate() method
656 n2 = Node("n2", [n1])
657 n3 = Node("n3", [n2])
659 tm = SCons.Taskmaster.Taskmaster([n3])
661 assert t.targets == [n1], t.targets
663 assert t.targets == [n3], list(map(str, t.targets))
664 assert t.top == 1, t.top
667 """Test the stop() method
669 Both default and overridden in a subclass.
675 n3 = Node("n3", [n1, n2])
677 tm = SCons.Taskmaster.Taskmaster([n3])
681 assert built_text == "n1 built", built_text
684 assert built_text == "n1 built really", built_text
687 assert tm.next_task() is None
689 class MyTM(SCons.Taskmaster.Taskmaster):
692 built_text = "MyTM.stop()"
693 SCons.Taskmaster.Taskmaster.stop(self)
697 n3 = Node("n3", [n1, n2])
701 tm.next_task().execute()
702 assert built_text == "n1 built"
705 assert built_text == "MyTM.stop()"
706 assert tm.next_task() is None
708 def test_executed(self):
709 """Test when a task has been executed
715 tm = SCons.Taskmaster.Taskmaster([n1])
719 n1.set_state(SCons.Node.executing)
724 assert s == SCons.Node.executed, s
725 assert built_text == "xxx really", built_text
726 assert visited_nodes == ['n1'], visited_nodes
729 tm = SCons.Taskmaster.Taskmaster([n2])
731 built_text = "should_not_change"
739 assert built_text == "should_not_change", built_text
740 assert visited_nodes == ['n2'], visited_nodes
744 n3.targets = [n3, n4]
745 tm = SCons.Taskmaster.Taskmaster([n3])
748 n3.set_state(SCons.Node.up_to_date)
749 n4.set_state(SCons.Node.executing)
754 assert s == SCons.Node.up_to_date, s
756 assert s == SCons.Node.executed, s
757 assert visited_nodes == ['n3', 'n4'], visited_nodes
759 def test_prepare(self):
760 """Test preparation of multiple Nodes for a task
764 tm = SCons.Taskmaster.Taskmaster([n1, n2])
766 # This next line is moderately bogus. We're just reaching
767 # in and setting the targets for this task to an array. The
768 # "right" way to do this would be to have the next_task() call
769 # set it up by having something that approximates a real Builder
770 # return this list--but that's more work than is probably
771 # warranted right now.
772 n1.get_executor().targets = [n1, n2]
779 tm = SCons.Taskmaster.Taskmaster([n3, n4])
781 # More bogus reaching in and setting the targets.
782 n3.set_state(SCons.Node.up_to_date)
783 n3.get_executor().targets = [n3, n4]
788 # If the Node has had an exception recorded while it was getting
789 # prepared, then prepare() should raise that exception.
790 class MyException(Exception):
795 tm = SCons.Taskmaster.Taskmaster([n5])
797 t.exception_set((MyException, "exception value"))
801 except MyException, e:
805 assert exc_caught, "did not catch expected MyException"
806 assert str(e) == "exception value", e
807 assert built_text is None, built_text
809 # Regression test, make sure we prepare not only
810 # all targets, but their side effects as well.
817 n6.side_effects = [ n8 ]
818 n7.side_effects = [ n9, n10 ]
820 tm = SCons.Taskmaster.Taskmaster([n6, n7])
822 # More bogus reaching in and setting the targets.
823 n6.get_executor().targets = [n6, n7]
831 # Make sure we call an Executor's prepare() method.
832 class ExceptionExecutor:
834 raise Exception, "Executor.prepare() exception"
835 def get_all_targets(self):
837 def get_all_children(self):
839 for node in self.nodes:
840 result.extend(node.children())
842 def get_all_prerequisites(self):
844 def get_action_side_effects(self):
848 n11.executor = ExceptionExecutor()
849 n11.executor.nodes = [n11]
850 tm = SCons.Taskmaster.Taskmaster([n11])
855 assert str(e) == "Executor.prepare() exception", e
857 raise AssertionError, "did not catch expected exception"
859 def test_execute(self):
860 """Test executing a task
866 tm = SCons.Taskmaster.Taskmaster([n1])
869 assert built_text == "n1 built", built_text
871 def raise_UserError():
872 raise SCons.Errors.UserError
874 n2.build = raise_UserError
875 tm = SCons.Taskmaster.Taskmaster([n2])
879 except SCons.Errors.UserError:
882 raise TestFailed, "did not catch expected UserError"
884 def raise_BuildError():
885 raise SCons.Errors.BuildError
887 n3.build = raise_BuildError
888 tm = SCons.Taskmaster.Taskmaster([n3])
892 except SCons.Errors.BuildError:
895 raise TestFailed, "did not catch expected BuildError"
897 # On a generic (non-BuildError) exception from a Builder,
898 # the target should throw a BuildError exception with the
899 # args set to the exception value, instance, and traceback.
900 def raise_OtherError():
903 n4.build = raise_OtherError
904 tm = SCons.Taskmaster.Taskmaster([n4])
908 except SCons.Errors.BuildError, e:
909 assert e.node == n4, e.node
910 assert e.errstr == "OtherError : ", e.errstr
911 assert len(e.exc_info) == 3, e.exc_info
912 exc_traceback = sys.exc_info()[2]
913 assert type(e.exc_info[2]) == type(exc_traceback), e.exc_info[2]
915 raise TestFailed, "did not catch expected BuildError"
922 tm = SCons.Taskmaster.Taskmaster([n5])
924 # This next line is moderately bogus. We're just reaching
925 # in and setting the targets for this task to an array. The
926 # "right" way to do this would be to have the next_task() call
927 # set it up by having something that approximates a real Builder
928 # return this list--but that's more work than is probably
929 # warranted right now.
932 assert built_text == "n5 built", built_text
933 assert cache_text == [], cache_text
941 tm = SCons.Taskmaster.Taskmaster([n7])
943 # This next line is moderately bogus. We're just reaching
944 # in and setting the targets for this task to an array. The
945 # "right" way to do this would be to have the next_task() call
946 # set it up by having something that approximates a real Builder
947 # return this list--but that's more work than is probably
948 # warranted right now.
951 assert built_text is None, built_text
952 assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
954 def test_exception(self):
955 """Test generic Taskmaster exception handling
959 tm = SCons.Taskmaster.Taskmaster([n1])
962 t.exception_set((1, 2))
963 exc_type, exc_value = t.exception
964 assert exc_type == 1, exc_type
965 assert exc_value == 2, exc_value
968 assert t.exception == 3
972 t.exception_set(None)
973 exc_type, exc_value, exc_tb = t.exception
974 assert exc_type is ZeroDivisionError, exc_type
976 "integer division or modulo",
977 "integer division or modulo by zero",
979 assert str(exc_value) in exception_values, exc_value
981 class Exception1(Exception):
984 t.exception_set((Exception1, None))
988 exc_type, exc_value = sys.exc_info()[:2]
989 assert exc_type == Exception1, exc_type
990 assert str(exc_value) == '', exc_value
992 assert 0, "did not catch expected exception"
994 class Exception2(Exception):
997 t.exception_set((Exception2, "xyzzy"))
1001 exc_type, exc_value = sys.exc_info()[:2]
1002 assert exc_type == Exception2, exc_type
1003 assert str(exc_value) == "xyzzy", exc_value
1005 assert 0, "did not catch expected exception"
1007 class Exception3(Exception):
1013 tb = sys.exc_info()[2]
1014 t.exception_set((Exception3, "arg", tb))
1018 exc_type, exc_value, exc_tb = sys.exc_info()
1019 assert exc_type == Exception3, exc_type
1020 assert str(exc_value) == "arg", exc_value
1022 x = traceback.extract_tb(tb)[-1]
1023 y = traceback.extract_tb(exc_tb)[-1]
1024 assert x == y, "x = %s, y = %s" % (x, y)
1026 assert 0, "did not catch expected exception"
1028 def test_postprocess(self):
1029 """Test postprocessing targets to give them a chance to clean up
1032 tm = SCons.Taskmaster.Taskmaster([n1])
1035 assert not n1.postprocessed
1037 assert n1.postprocessed
1041 tm = SCons.Taskmaster.Taskmaster([n2, n3])
1043 assert not n2.postprocessed
1044 assert not n3.postprocessed
1047 assert n2.postprocessed
1048 assert not n3.postprocessed
1051 assert n2.postprocessed
1052 assert n3.postprocessed
1054 def test_trace(self):
1055 """Test Taskmaster tracing
1059 trace = StringIO.StringIO()
1062 n3 = Node("n3", [n1, n2])
1063 tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1068 n1.set_state(SCons.Node.executed)
1073 n2.set_state(SCons.Node.executed)
1081 value = trace.getvalue()
1084 Taskmaster: Looking for a node to evaluate
1085 Taskmaster: Considering node <no_state 0 'n1'> and its children:
1086 Taskmaster: Evaluating <pending 0 'n1'>
1088 Task.make_ready_current(): node <pending 0 'n1'>
1089 Task.prepare(): node <executing 0 'n1'>
1090 Task.execute(): node <executing 0 'n1'>
1091 Task.postprocess(): node <executing 0 'n1'>
1093 Taskmaster: Looking for a node to evaluate
1094 Taskmaster: Considering node <executed 0 'n1'> and its children:
1095 Taskmaster: already handled (executed)
1096 Taskmaster: Considering node <no_state 0 'n3'> and its children:
1097 Taskmaster: <executed 0 'n1'>
1098 Taskmaster: <no_state 0 'n2'>
1099 Taskmaster: adjusted ref count: <pending 1 'n3'>, child 'n2'
1100 Taskmaster: Considering node <no_state 0 'n2'> and its children:
1101 Taskmaster: Evaluating <pending 0 'n2'>
1103 Task.make_ready_current(): node <pending 0 'n2'>
1104 Task.prepare(): node <executing 0 'n2'>
1105 Task.execute(): node <executing 0 'n2'>
1106 Task.postprocess(): node <executing 0 'n2'>
1107 Task.postprocess(): removing <executing 0 'n2'>
1108 Task.postprocess(): adjusted parent ref count <pending 0 'n3'>
1110 Taskmaster: Looking for a node to evaluate
1111 Taskmaster: Considering node <pending 0 'n3'> and its children:
1112 Taskmaster: <executed 0 'n1'>
1113 Taskmaster: <executed 0 'n2'>
1114 Taskmaster: Evaluating <pending 0 'n3'>
1116 Task.make_ready_current(): node <pending 0 'n3'>
1117 Task.prepare(): node <executing 0 'n3'>
1118 Task.execute(): node <executing 0 'n3'>
1119 Task.postprocess(): node <executing 0 'n3'>
1121 Taskmaster: Looking for a node to evaluate
1122 Taskmaster: No candidate anymore.
1125 assert value == expect, value
1129 if __name__ == "__main__":
1130 suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1131 if not unittest.TextTestRunner().run(suite).wasSuccessful():
1136 # indent-tabs-mode:nil
1138 # vim: set expandtab tabstop=4 shiftwidth=4: