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__"
32 import SCons.Taskmaster
43 def __init__(self, name, kids = [], scans = []):
51 self.prerequisites = []
53 def targets(self, node):
55 self.builder = Builder()
58 self.state = SCons.Node.no_state
61 self.waiting_parents = set()
62 self.waiting_s_e = set()
64 self.side_effects = []
66 self.postprocessed = None
69 self.always_build = None
71 def disambiguate(self):
74 def push_to_cache(self):
77 def retrieve_from_cache(self):
80 cache_text.append(self.name + " retrieved")
91 built_text = self.name + " built"
96 built_text = built_text + " really"
98 def has_builder(self):
99 return not self.builder is None
101 def is_derived(self):
102 return self.has_builder or self.side_effect
104 def alter_targets(self):
105 return self.alttargets, None
109 visited_nodes.append(self.name)
119 scan_called = scan_called + 1
120 self.kids = self.kids + self.scans
123 def scanner_key(self):
126 def add_to_waiting_parents(self, node):
127 wp = self.waiting_parents
136 def set_state(self, state):
139 def set_bsig(self, bsig):
142 def set_csig(self, csig):
145 def store_csig(self):
148 def store_bsig(self):
151 def is_pseudo_derived(self):
154 def is_up_to_date(self):
155 return self._current_val
157 def depends_on(self, nodes):
159 if node in self.kids:
166 def postprocess(self):
167 self.postprocessed = 1
168 self.waiting_parents = set()
170 def get_executor(self):
171 if not hasattr(self, 'executor'):
175 def get_action_targets(self):
177 def get_all_targets(self):
179 def get_all_children(self):
181 for node in self.targets:
182 result.extend(node.children())
184 def get_all_prerequisites(self):
186 def get_action_side_effects(self):
188 self.executor = Executor()
189 self.executor.targets = self.targets
192 class OtherError(Exception):
195 class MyException(Exception):
199 class TaskmasterTestCase(unittest.TestCase):
201 def test_next_task(self):
202 """Test fetching the next task
207 tm = SCons.Taskmaster.Taskmaster([n1, n1])
216 n3 = Node("n3", [n1, n2])
218 tm = SCons.Taskmaster.Taskmaster([n3])
223 assert built_text == "n1 built", built_text
230 assert built_text == "n2 built", built_text
237 assert built_text == "n3 built", built_text
241 assert tm.next_task() is None
243 built_text = "up to date: "
246 class MyTask(SCons.Taskmaster.Task):
249 if self.targets[0].get_state() == SCons.Node.up_to_date:
251 built_text = self.targets[0].name + " up-to-date top"
253 built_text = self.targets[0].name + " up-to-date"
255 self.targets[0].build()
257 n1.set_state(SCons.Node.no_state)
259 n2.set_state(SCons.Node.no_state)
261 n3.set_state(SCons.Node.no_state)
263 tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
268 assert built_text == "n1 up-to-date", built_text
275 assert built_text == "n2 up-to-date", built_text
282 assert built_text == "n3 up-to-date top", built_text
286 assert tm.next_task() is None
291 n3 = Node("n3", [n1, n2])
293 n5 = Node("n5", [n3, n4])
294 tm = SCons.Taskmaster.Taskmaster([n5])
297 assert t1.get_target() == n1
300 assert t2.get_target() == n2
303 assert t4.get_target() == n4
312 assert t3.get_target() == n3
317 assert t5.get_target() == n5, t5.get_target()
321 assert tm.next_task() is None
325 n4.set_state(SCons.Node.executed)
326 tm = SCons.Taskmaster.Taskmaster([n4])
327 assert tm.next_task() is None
330 n2 = Node("n2", [n1])
331 tm = SCons.Taskmaster.Taskmaster([n2,n2])
336 assert tm.next_task() is None
341 n3 = Node("n3", [n1], [n2])
342 tm = SCons.Taskmaster.Taskmaster([n3])
344 target = t.get_target()
345 assert target == n1, target
349 target = t.get_target()
350 assert target == n2, target
354 target = t.get_target()
355 assert target == n3, target
358 assert tm.next_task() is None
362 n3 = Node("n3", [n1, n2])
363 n4 = Node("n4", [n3])
364 n5 = Node("n5", [n3])
367 tm = SCons.Taskmaster.Taskmaster([n4])
369 assert t.get_target() == n1
373 assert t.get_target() == n2
377 assert t.get_target() == n3
381 assert t.get_target() == n4
384 assert tm.next_task() is None
385 assert scan_called == 4, scan_called
387 tm = SCons.Taskmaster.Taskmaster([n5])
389 assert t.get_target() == n5, t.get_target()
391 assert tm.next_task() is None
392 assert scan_called == 5, scan_called
397 n4 = Node("n4", [n1,n2,n3])
398 n5 = Node("n5", [n4])
400 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
401 tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
403 assert t.get_target() == n1
404 assert n4.state == SCons.Node.executing, n4.state
408 assert t.get_target() == n2
412 assert t.get_target() == n3
416 assert t.get_target() == n4
420 assert t.get_target() == n5
421 assert not tm.next_task()
428 n4 = Node("n4", [n1,n2,n3])
429 def reverse(dependencies):
430 dependencies.reverse()
432 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
434 assert t.get_target() == n3, t.get_target()
438 assert t.get_target() == n2, t.get_target()
442 assert t.get_target() == n1, t.get_target()
446 assert t.get_target() == n4, t.get_target()
455 tm = SCons.Taskmaster.Taskmaster([n5])
457 assert t.get_target() == n5
461 tm = SCons.Taskmaster.Taskmaster([n6])
463 assert t.get_target() == n7
467 assert t.get_target() == n6
472 n2 = Node("n2", [n1])
473 n1.set_state(SCons.Node.failed)
474 tm = SCons.Taskmaster.Taskmaster([n2])
475 assert tm.next_task() is None
479 n1.targets = [n1, n2]
481 tm = SCons.Taskmaster.Taskmaster([n1])
487 assert s == SCons.Node.executed, s
489 assert s == SCons.Node.executed, s
492 def test_make_ready_out_of_date(self):
493 """Test the Task.make_ready() method's list of out-of-date Nodes
496 def TaskGen(tm, targets, top, node, ood=ood):
497 class MyTask(SCons.Taskmaster.Task):
498 def make_ready(self):
499 SCons.Taskmaster.Task.make_ready(self)
500 self.ood.extend(self.out_of_date)
501 t = MyTask(tm, targets, top, node)
514 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4, a5],
519 assert ood == [n1], ood
523 assert ood == [], ood
527 assert ood == [n3], ood
531 assert ood == [], ood
535 assert ood == [a5], ood
537 def test_make_ready_exception(self):
538 """Test handling exceptions from Task.make_ready()
540 class MyTask(SCons.Taskmaster.Task):
541 def make_ready(self):
542 raise MyException("from make_ready()")
545 tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
547 exc_type, exc_value, exc_tb = t.exception
548 assert exc_type == MyException, repr(exc_type)
549 assert str(exc_value) == "from make_ready()", exc_value
552 def test_make_ready_all(self):
553 """Test the make_ready_all() method"""
554 class MyTask(SCons.Taskmaster.Task):
555 make_ready = SCons.Taskmaster.Task.make_ready_all
564 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
567 target = t.get_target()
568 assert target is n1, target
569 assert target.state == SCons.Node.executing, target.state
571 target = t.get_target()
572 assert target is c2, target
573 assert target.state == SCons.Node.up_to_date, target.state
575 target = t.get_target()
576 assert target is n3, target
577 assert target.state == SCons.Node.executing, target.state
579 target = t.get_target()
580 assert target is c4, target
581 assert target.state == SCons.Node.up_to_date, target.state
590 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
594 target = t.get_target()
595 assert target is n1, target
596 assert target.state == SCons.Node.executing, target.state
598 target = t.get_target()
599 assert target is c2, target
600 assert target.state == SCons.Node.executing, target.state
602 target = t.get_target()
603 assert target is n3, target
604 assert target.state == SCons.Node.executing, target.state
606 target = t.get_target()
607 assert target is c4, target
608 assert target.state == SCons.Node.executing, target.state
613 def test_children_errors(self):
614 """Test errors when fetching the children of a node.
616 class StopNode(Node):
618 raise SCons.Errors.StopError("stop!")
619 class ExitNode(Node):
624 tm = SCons.Taskmaster.Taskmaster([n1])
626 exc_type, exc_value, exc_tb = t.exception
627 assert exc_type == SCons.Errors.StopError, repr(exc_type)
628 assert str(exc_value) == "stop!", exc_value
631 tm = SCons.Taskmaster.Taskmaster([n2])
633 exc_type, exc_value = t.exception
634 assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
635 assert exc_value.node == n2, exc_value.node
636 assert exc_value.status == 77, exc_value.status
638 def test_cycle_detection(self):
639 """Test detecting dependency cycles
642 n2 = Node("n2", [n1])
643 n3 = Node("n3", [n2])
646 tm = SCons.Taskmaster.Taskmaster([n3])
649 except SCons.Errors.UserError, e:
650 assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
652 assert 'Did not catch expected UserError'
654 def test_next_top_level_candidate(self):
655 """Test the next_top_level_candidate() method
658 n2 = Node("n2", [n1])
659 n3 = Node("n3", [n2])
661 tm = SCons.Taskmaster.Taskmaster([n3])
663 assert t.targets == [n1], t.targets
665 assert t.targets == [n3], list(map(str, t.targets))
666 assert t.top == 1, t.top
669 """Test the stop() method
671 Both default and overridden in a subclass.
677 n3 = Node("n3", [n1, n2])
679 tm = SCons.Taskmaster.Taskmaster([n3])
683 assert built_text == "n1 built", built_text
686 assert built_text == "n1 built really", built_text
689 assert tm.next_task() is None
691 class MyTM(SCons.Taskmaster.Taskmaster):
694 built_text = "MyTM.stop()"
695 SCons.Taskmaster.Taskmaster.stop(self)
699 n3 = Node("n3", [n1, n2])
703 tm.next_task().execute()
704 assert built_text == "n1 built"
707 assert built_text == "MyTM.stop()"
708 assert tm.next_task() is None
710 def test_executed(self):
711 """Test when a task has been executed
717 tm = SCons.Taskmaster.Taskmaster([n1])
721 n1.set_state(SCons.Node.executing)
726 assert s == SCons.Node.executed, s
727 assert built_text == "xxx really", built_text
728 assert visited_nodes == ['n1'], visited_nodes
731 tm = SCons.Taskmaster.Taskmaster([n2])
733 built_text = "should_not_change"
741 assert built_text == "should_not_change", built_text
742 assert visited_nodes == ['n2'], visited_nodes
746 n3.targets = [n3, n4]
747 tm = SCons.Taskmaster.Taskmaster([n3])
750 n3.set_state(SCons.Node.up_to_date)
751 n4.set_state(SCons.Node.executing)
756 assert s == SCons.Node.up_to_date, s
758 assert s == SCons.Node.executed, s
759 assert visited_nodes == ['n3', 'n4'], visited_nodes
761 def test_prepare(self):
762 """Test preparation of multiple Nodes for a task
766 tm = SCons.Taskmaster.Taskmaster([n1, n2])
768 # This next line is moderately bogus. We're just reaching
769 # in and setting the targets for this task to an array. The
770 # "right" way to do this would be to have the next_task() call
771 # set it up by having something that approximates a real Builder
772 # return this list--but that's more work than is probably
773 # warranted right now.
774 n1.get_executor().targets = [n1, n2]
781 tm = SCons.Taskmaster.Taskmaster([n3, n4])
783 # More bogus reaching in and setting the targets.
784 n3.set_state(SCons.Node.up_to_date)
785 n3.get_executor().targets = [n3, n4]
790 # If the Node has had an exception recorded while it was getting
791 # prepared, then prepare() should raise that exception.
792 class MyException(Exception):
797 tm = SCons.Taskmaster.Taskmaster([n5])
799 t.exception_set((MyException, "exception value"))
803 except MyException, e:
807 assert exc_caught, "did not catch expected MyException"
808 assert str(e) == "exception value", e
809 assert built_text is None, built_text
811 # Regression test, make sure we prepare not only
812 # all targets, but their side effects as well.
819 n6.side_effects = [ n8 ]
820 n7.side_effects = [ n9, n10 ]
822 tm = SCons.Taskmaster.Taskmaster([n6, n7])
824 # More bogus reaching in and setting the targets.
825 n6.get_executor().targets = [n6, n7]
833 # Make sure we call an Executor's prepare() method.
834 class ExceptionExecutor:
836 raise Exception("Executor.prepare() exception")
837 def get_all_targets(self):
839 def get_all_children(self):
841 for node in self.nodes:
842 result.extend(node.children())
844 def get_all_prerequisites(self):
846 def get_action_side_effects(self):
850 n11.executor = ExceptionExecutor()
851 n11.executor.nodes = [n11]
852 tm = SCons.Taskmaster.Taskmaster([n11])
857 assert str(e) == "Executor.prepare() exception", e
859 raise AssertionError("did not catch expected exception")
861 def test_execute(self):
862 """Test executing a task
868 tm = SCons.Taskmaster.Taskmaster([n1])
871 assert built_text == "n1 built", built_text
873 def raise_UserError():
874 raise SCons.Errors.UserError
876 n2.build = raise_UserError
877 tm = SCons.Taskmaster.Taskmaster([n2])
881 except SCons.Errors.UserError:
884 raise TestFailed("did not catch expected UserError")
886 def raise_BuildError():
887 raise SCons.Errors.BuildError
889 n3.build = raise_BuildError
890 tm = SCons.Taskmaster.Taskmaster([n3])
894 except SCons.Errors.BuildError:
897 raise TestFailed("did not catch expected BuildError")
899 # On a generic (non-BuildError) exception from a Builder,
900 # the target should throw a BuildError exception with the
901 # args set to the exception value, instance, and traceback.
902 def raise_OtherError():
905 n4.build = raise_OtherError
906 tm = SCons.Taskmaster.Taskmaster([n4])
910 except SCons.Errors.BuildError, e:
911 assert e.node == n4, e.node
912 assert e.errstr == "OtherError : ", e.errstr
913 assert len(e.exc_info) == 3, e.exc_info
914 exc_traceback = sys.exc_info()[2]
915 assert isinstance(e.exc_info[2], type(exc_traceback)), e.exc_info[2]
917 raise TestFailed("did not catch expected BuildError")
924 tm = SCons.Taskmaster.Taskmaster([n5])
926 # This next line is moderately bogus. We're just reaching
927 # in and setting the targets for this task to an array. The
928 # "right" way to do this would be to have the next_task() call
929 # set it up by having something that approximates a real Builder
930 # return this list--but that's more work than is probably
931 # warranted right now.
934 assert built_text == "n5 built", built_text
935 assert cache_text == [], cache_text
943 tm = SCons.Taskmaster.Taskmaster([n7])
945 # This next line is moderately bogus. We're just reaching
946 # in and setting the targets for this task to an array. The
947 # "right" way to do this would be to have the next_task() call
948 # set it up by having something that approximates a real Builder
949 # return this list--but that's more work than is probably
950 # warranted right now.
953 assert built_text is None, built_text
954 assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
956 def test_exception(self):
957 """Test generic Taskmaster exception handling
961 tm = SCons.Taskmaster.Taskmaster([n1])
964 t.exception_set((1, 2))
965 exc_type, exc_value = t.exception
966 assert exc_type == 1, exc_type
967 assert exc_value == 2, exc_value
970 assert t.exception == 3
974 t.exception_set(None)
975 exc_type, exc_value, exc_tb = t.exception
976 assert exc_type is ZeroDivisionError, exc_type
978 "integer division or modulo",
979 "integer division or modulo by zero",
981 assert str(exc_value) in exception_values, exc_value
983 class Exception1(Exception):
986 t.exception_set((Exception1, None))
990 exc_type, exc_value = sys.exc_info()[:2]
991 assert exc_type == Exception1, exc_type
992 assert str(exc_value) == '', exc_value
994 assert 0, "did not catch expected exception"
996 class Exception2(Exception):
999 t.exception_set((Exception2, "xyzzy"))
1003 exc_type, exc_value = sys.exc_info()[:2]
1004 assert exc_type == Exception2, exc_type
1005 assert str(exc_value) == "xyzzy", exc_value
1007 assert 0, "did not catch expected exception"
1009 class Exception3(Exception):
1015 tb = sys.exc_info()[2]
1016 t.exception_set((Exception3, "arg", tb))
1020 exc_type, exc_value, exc_tb = sys.exc_info()
1021 assert exc_type == Exception3, exc_type
1022 assert str(exc_value) == "arg", exc_value
1024 x = traceback.extract_tb(tb)[-1]
1025 y = traceback.extract_tb(exc_tb)[-1]
1026 assert x == y, "x = %s, y = %s" % (x, y)
1028 assert 0, "did not catch expected exception"
1030 def test_postprocess(self):
1031 """Test postprocessing targets to give them a chance to clean up
1034 tm = SCons.Taskmaster.Taskmaster([n1])
1037 assert not n1.postprocessed
1039 assert n1.postprocessed
1043 tm = SCons.Taskmaster.Taskmaster([n2, n3])
1045 assert not n2.postprocessed
1046 assert not n3.postprocessed
1049 assert n2.postprocessed
1050 assert not n3.postprocessed
1053 assert n2.postprocessed
1054 assert n3.postprocessed
1056 def test_trace(self):
1057 """Test Taskmaster tracing
1061 trace = io.StringIO()
1064 n3 = Node("n3", [n1, n2])
1065 tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1070 n1.set_state(SCons.Node.executed)
1075 n2.set_state(SCons.Node.executed)
1083 value = trace.getvalue()
1086 Taskmaster: Looking for a node to evaluate
1087 Taskmaster: Considering node <no_state 0 'n1'> and its children:
1088 Taskmaster: Evaluating <pending 0 'n1'>
1090 Task.make_ready_current(): node <pending 0 'n1'>
1091 Task.prepare(): node <executing 0 'n1'>
1092 Task.execute(): node <executing 0 'n1'>
1093 Task.postprocess(): node <executing 0 'n1'>
1095 Taskmaster: Looking for a node to evaluate
1096 Taskmaster: Considering node <executed 0 'n1'> and its children:
1097 Taskmaster: already handled (executed)
1098 Taskmaster: Considering node <no_state 0 'n3'> and its children:
1099 Taskmaster: <executed 0 'n1'>
1100 Taskmaster: <no_state 0 'n2'>
1101 Taskmaster: adjusted ref count: <pending 1 'n3'>, child 'n2'
1102 Taskmaster: Considering node <no_state 0 'n2'> and its children:
1103 Taskmaster: Evaluating <pending 0 'n2'>
1105 Task.make_ready_current(): node <pending 0 'n2'>
1106 Task.prepare(): node <executing 0 'n2'>
1107 Task.execute(): node <executing 0 'n2'>
1108 Task.postprocess(): node <executing 0 'n2'>
1109 Task.postprocess(): removing <executing 0 'n2'>
1110 Task.postprocess(): adjusted parent ref count <pending 0 'n3'>
1112 Taskmaster: Looking for a node to evaluate
1113 Taskmaster: Considering node <pending 0 'n3'> and its children:
1114 Taskmaster: <executed 0 'n1'>
1115 Taskmaster: <executed 0 'n2'>
1116 Taskmaster: Evaluating <pending 0 'n3'>
1118 Task.make_ready_current(): node <pending 0 'n3'>
1119 Task.prepare(): node <executing 0 'n3'>
1120 Task.execute(): node <executing 0 'n3'>
1121 Task.postprocess(): node <executing 0 'n3'>
1123 Taskmaster: Looking for a node to evaluate
1124 Taskmaster: No candidate anymore.
1127 assert value == expect, value
1131 if __name__ == "__main__":
1132 suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1133 if not unittest.TextTestRunner().run(suite).wasSuccessful():
1138 # indent-tabs-mode:nil
1140 # vim: set expandtab tabstop=4 shiftwidth=4: