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 self.executor = Executor()
170 self.executor.targets = self.targets
173 class OtherError(Exception):
176 class MyException(Exception):
180 class TaskmasterTestCase(unittest.TestCase):
182 def test_next_task(self):
183 """Test fetching the next task
188 tm = SCons.Taskmaster.Taskmaster([n1, n1])
197 n3 = Node("n3", [n1, n2])
199 tm = SCons.Taskmaster.Taskmaster([n3])
204 assert built_text == "n1 built", built_text
211 assert built_text == "n2 built", built_text
218 assert built_text == "n3 built", built_text
222 assert tm.next_task() == None
224 built_text = "up to date: "
227 class MyTask(SCons.Taskmaster.Task):
230 if self.targets[0].get_state() == SCons.Node.up_to_date:
232 built_text = self.targets[0].name + " up-to-date top"
234 built_text = self.targets[0].name + " up-to-date"
236 self.targets[0].build()
238 n1.set_state(SCons.Node.no_state)
240 n2.set_state(SCons.Node.no_state)
242 n3.set_state(SCons.Node.no_state)
244 tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
249 assert built_text == "n1 up-to-date", built_text
256 assert built_text == "n2 up-to-date", built_text
263 assert built_text == "n3 up-to-date top", built_text
267 assert tm.next_task() == None
272 n3 = Node("n3", [n1, n2])
274 n5 = Node("n5", [n3, n4])
275 tm = SCons.Taskmaster.Taskmaster([n5])
278 assert t1.get_target() == n1
281 assert t2.get_target() == n2
284 assert t4.get_target() == n4
293 assert t3.get_target() == n3
298 assert t5.get_target() == n5, t5.get_target()
302 assert tm.next_task() == None
306 n4.set_state(SCons.Node.executed)
307 tm = SCons.Taskmaster.Taskmaster([n4])
308 assert tm.next_task() == None
311 n2 = Node("n2", [n1])
312 tm = SCons.Taskmaster.Taskmaster([n2,n2])
317 assert tm.next_task() == None
322 n3 = Node("n3", [n1], [n2])
323 tm = SCons.Taskmaster.Taskmaster([n3])
325 target = t.get_target()
326 assert target == n1, target
330 target = t.get_target()
331 assert target == n2, target
335 target = t.get_target()
336 assert target == n3, target
339 assert tm.next_task() == None
343 n3 = Node("n3", [n1, n2])
344 n4 = Node("n4", [n3])
345 n5 = Node("n5", [n3])
348 tm = SCons.Taskmaster.Taskmaster([n4])
350 assert t.get_target() == n1
354 assert t.get_target() == n2
358 assert t.get_target() == n3
362 assert t.get_target() == n4
365 assert tm.next_task() == None
366 assert scan_called == 4, scan_called
368 tm = SCons.Taskmaster.Taskmaster([n5])
370 assert t.get_target() == n5, t.get_target()
372 assert tm.next_task() == None
373 assert scan_called == 5, scan_called
378 n4 = Node("n4", [n1,n2,n3])
379 n5 = Node("n5", [n4])
381 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
382 tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
384 assert t.get_target() == n1
385 assert n4.state == SCons.Node.executing, n4.state
389 assert t.get_target() == n2
393 assert t.get_target() == n3
397 assert t.get_target() == n4
401 assert t.get_target() == n5
402 assert not tm.next_task()
409 n4 = Node("n4", [n1,n2,n3])
410 def reverse(dependencies):
411 dependencies.reverse()
413 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
415 assert t.get_target() == n3, t.get_target()
419 assert t.get_target() == n2, t.get_target()
423 assert t.get_target() == n1, t.get_target()
427 assert t.get_target() == n4, t.get_target()
436 tm = SCons.Taskmaster.Taskmaster([n5])
438 assert t.get_target() == n5
442 tm = SCons.Taskmaster.Taskmaster([n6])
444 assert t.get_target() == n7
448 assert t.get_target() == n6
453 n2 = Node("n2", [n1])
454 n1.set_state(SCons.Node.failed)
455 tm = SCons.Taskmaster.Taskmaster([n2])
456 assert tm.next_task() is None
460 n1.targets = [n1, n2]
462 tm = SCons.Taskmaster.Taskmaster([n1])
468 assert s == SCons.Node.executed, s
470 assert s == SCons.Node.executed, s
473 def test_make_ready_out_of_date(self):
474 """Test the Task.make_ready() method's list of out-of-date Nodes
477 def TaskGen(tm, targets, top, node, ood=ood):
478 class MyTask(SCons.Taskmaster.Task):
479 def make_ready(self):
480 SCons.Taskmaster.Task.make_ready(self)
481 self.ood.extend(self.out_of_date)
482 t = MyTask(tm, targets, top, node)
495 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4, a5],
500 assert ood == [n1], ood
504 assert ood == [], ood
508 assert ood == [n3], ood
512 assert ood == [], ood
516 assert ood == [a5], ood
518 def test_make_ready_exception(self):
519 """Test handling exceptions from Task.make_ready()
521 class MyTask(SCons.Taskmaster.Task):
522 def make_ready(self):
523 raise MyException, "from make_ready()"
526 tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
528 exc_type, exc_value, exc_tb = t.exception
529 assert exc_type == MyException, repr(exc_type)
530 assert str(exc_value) == "from make_ready()", exc_value
533 def test_make_ready_all(self):
534 """Test the make_ready_all() method"""
535 class MyTask(SCons.Taskmaster.Task):
536 make_ready = SCons.Taskmaster.Task.make_ready_all
545 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
548 target = t.get_target()
549 assert target is n1, target
550 assert target.state == SCons.Node.executing, target.state
552 target = t.get_target()
553 assert target is c2, target
554 assert target.state == SCons.Node.up_to_date, target.state
556 target = t.get_target()
557 assert target is n3, target
558 assert target.state == SCons.Node.executing, target.state
560 target = t.get_target()
561 assert target is c4, target
562 assert target.state == SCons.Node.up_to_date, target.state
571 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
575 target = t.get_target()
576 assert target is n1, target
577 assert target.state == SCons.Node.executing, target.state
579 target = t.get_target()
580 assert target is c2, target
581 assert target.state == SCons.Node.executing, target.state
583 target = t.get_target()
584 assert target is n3, target
585 assert target.state == SCons.Node.executing, target.state
587 target = t.get_target()
588 assert target is c4, target
589 assert target.state == SCons.Node.executing, target.state
594 def test_children_errors(self):
595 """Test errors when fetching the children of a node.
597 class StopNode(Node):
599 raise SCons.Errors.StopError, "stop!"
600 class ExitNode(Node):
605 tm = SCons.Taskmaster.Taskmaster([n1])
607 exc_type, exc_value, exc_tb = t.exception
608 assert exc_type == SCons.Errors.StopError, repr(exc_type)
609 assert str(exc_value) == "stop!", exc_value
612 tm = SCons.Taskmaster.Taskmaster([n2])
614 exc_type, exc_value = t.exception
615 assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
616 assert exc_value.node == n2, exc_value.node
617 assert exc_value.status == 77, exc_value.status
619 def test_cycle_detection(self):
620 """Test detecting dependency cycles
623 n2 = Node("n2", [n1])
624 n3 = Node("n3", [n2])
627 tm = SCons.Taskmaster.Taskmaster([n3])
630 except SCons.Errors.UserError, e:
631 assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
633 assert 'Did not catch expected UserError'
635 def test_next_top_level_candidate(self):
636 """Test the next_top_level_candidate() method
639 n2 = Node("n2", [n1])
640 n3 = Node("n3", [n2])
642 tm = SCons.Taskmaster.Taskmaster([n3])
644 assert t.targets == [n1], t.targets
646 assert t.targets == [n3], map(str, t.targets)
647 assert t.top == 1, t.top
650 """Test the stop() method
652 Both default and overridden in a subclass.
658 n3 = Node("n3", [n1, n2])
660 tm = SCons.Taskmaster.Taskmaster([n3])
664 assert built_text == "n1 built", built_text
667 assert built_text == "n1 built really", built_text
670 assert tm.next_task() is None
672 class MyTM(SCons.Taskmaster.Taskmaster):
675 built_text = "MyTM.stop()"
676 SCons.Taskmaster.Taskmaster.stop(self)
680 n3 = Node("n3", [n1, n2])
684 tm.next_task().execute()
685 assert built_text == "n1 built"
688 assert built_text == "MyTM.stop()"
689 assert tm.next_task() is None
691 def test_executed(self):
692 """Test when a task has been executed
698 tm = SCons.Taskmaster.Taskmaster([n1])
702 n1.set_state(SCons.Node.executing)
707 assert s == SCons.Node.executed, s
708 assert built_text == "xxx really", built_text
709 assert visited_nodes == ['n1'], visited_nodes
712 tm = SCons.Taskmaster.Taskmaster([n2])
714 built_text = "should_not_change"
722 assert built_text == "should_not_change", built_text
723 assert visited_nodes == ['n2'], visited_nodes
727 n3.targets = [n3, n4]
728 tm = SCons.Taskmaster.Taskmaster([n3])
731 n3.set_state(SCons.Node.up_to_date)
732 n4.set_state(SCons.Node.executing)
737 assert s == SCons.Node.up_to_date, s
739 assert s == SCons.Node.executed, s
740 assert visited_nodes == ['n3', 'n4'], visited_nodes
742 def test_prepare(self):
743 """Test preparation of multiple Nodes for a task
747 tm = SCons.Taskmaster.Taskmaster([n1, n2])
749 # This next line is moderately bogus. We're just reaching
750 # in and setting the targets for this task to an array. The
751 # "right" way to do this would be to have the next_task() call
752 # set it up by having something that approximates a real Builder
753 # return this list--but that's more work than is probably
754 # warranted right now.
762 tm = SCons.Taskmaster.Taskmaster([n3, n4])
764 # More bogus reaching in and setting the targets.
765 n3.set_state(SCons.Node.up_to_date)
771 # If the Node has had an exception recorded while it was getting
772 # prepared, then prepare() should raise that exception.
773 class MyException(Exception):
778 tm = SCons.Taskmaster.Taskmaster([n5])
780 t.exception_set((MyException, "exception value"))
784 except MyException, e:
788 assert exc_caught, "did not catch expected MyException"
789 assert str(e) == "exception value", e
790 assert built_text is None, built_text
792 # Regression test, make sure we prepare not only
793 # all targets, but their side effects as well.
800 n6.side_effects = [ n8 ]
801 n7.side_effects = [ n9, n10 ]
803 tm = SCons.Taskmaster.Taskmaster([n6, n7])
805 # More bogus reaching in and setting the targets.
814 # Make sure we call an Executor's prepare() method.
815 class ExceptionExecutor:
817 raise Exception, "Executor.prepare() exception"
820 n11.executor = ExceptionExecutor()
821 tm = SCons.Taskmaster.Taskmaster([n11])
826 assert str(e) == "Executor.prepare() exception", e
828 raise AssertionError, "did not catch expected exception"
830 def test_execute(self):
831 """Test executing a task
837 tm = SCons.Taskmaster.Taskmaster([n1])
840 assert built_text == "n1 built", built_text
842 def raise_UserError():
843 raise SCons.Errors.UserError
845 n2.build = raise_UserError
846 tm = SCons.Taskmaster.Taskmaster([n2])
850 except SCons.Errors.UserError:
853 raise TestFailed, "did not catch expected UserError"
855 def raise_BuildError():
856 raise SCons.Errors.BuildError
858 n3.build = raise_BuildError
859 tm = SCons.Taskmaster.Taskmaster([n3])
863 except SCons.Errors.BuildError:
866 raise TestFailed, "did not catch expected BuildError"
868 # On a generic (non-BuildError) exception from a Builder,
869 # the target should throw a BuildError exception with the
870 # args set to the exception value, instance, and traceback.
871 def raise_OtherError():
874 n4.build = raise_OtherError
875 tm = SCons.Taskmaster.Taskmaster([n4])
879 except SCons.Errors.TaskmasterException, e:
880 assert e.node == n4, e.node
881 assert e.errstr == "Exception", e.errstr
882 assert len(e.exc_info) == 3, e.exc_info
883 exc_traceback = sys.exc_info()[2]
884 assert type(e.exc_info[2]) == type(exc_traceback), e.exc_info[2]
886 raise TestFailed, "did not catch expected BuildError"
893 tm = SCons.Taskmaster.Taskmaster([n5])
895 # This next line is moderately bogus. We're just reaching
896 # in and setting the targets for this task to an array. The
897 # "right" way to do this would be to have the next_task() call
898 # set it up by having something that approximates a real Builder
899 # return this list--but that's more work than is probably
900 # warranted right now.
903 assert built_text == "n5 built", built_text
904 assert cache_text == [], cache_text
912 tm = SCons.Taskmaster.Taskmaster([n7])
914 # This next line is moderately bogus. We're just reaching
915 # in and setting the targets for this task to an array. The
916 # "right" way to do this would be to have the next_task() call
917 # set it up by having something that approximates a real Builder
918 # return this list--but that's more work than is probably
919 # warranted right now.
922 assert built_text is None, built_text
923 assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
925 def test_exception(self):
926 """Test generic Taskmaster exception handling
930 tm = SCons.Taskmaster.Taskmaster([n1])
933 t.exception_set((1, 2))
934 exc_type, exc_value = t.exception
935 assert exc_type == 1, exc_type
936 assert exc_value == 2, exc_value
939 assert t.exception == 3
943 t.exception_set(None)
944 exc_type, exc_value, exc_tb = t.exception
945 assert exc_type is ZeroDivisionError, exc_type
947 "integer division or modulo",
948 "integer division or modulo by zero",
950 assert str(exc_value) in exception_values, exc_value
952 class Exception1(Exception):
955 t.exception_set((Exception1, None))
959 exc_type, exc_value = sys.exc_info()[:2]
960 assert exc_type == Exception1, exc_type
961 assert str(exc_value) == '', exc_value
963 assert 0, "did not catch expected exception"
965 class Exception2(Exception):
968 t.exception_set((Exception2, "xyzzy"))
972 exc_type, exc_value = sys.exc_info()[:2]
973 assert exc_type == Exception2, exc_type
974 assert str(exc_value) == "xyzzy", exc_value
976 assert 0, "did not catch expected exception"
978 class Exception3(Exception):
984 tb = sys.exc_info()[2]
985 t.exception_set((Exception3, "arg", tb))
989 exc_type, exc_value, exc_tb = sys.exc_info()
990 assert exc_type == Exception3, exc_type
991 assert str(exc_value) == "arg", exc_value
993 x = traceback.extract_tb(tb)[-1]
994 y = traceback.extract_tb(exc_tb)[-1]
995 assert x == y, "x = %s, y = %s" % (x, y)
997 assert 0, "did not catch expected exception"
999 def test_postprocess(self):
1000 """Test postprocessing targets to give them a chance to clean up
1003 tm = SCons.Taskmaster.Taskmaster([n1])
1006 assert not n1.postprocessed
1008 assert n1.postprocessed
1012 tm = SCons.Taskmaster.Taskmaster([n2, n3])
1014 assert not n2.postprocessed
1015 assert not n3.postprocessed
1018 assert n2.postprocessed
1019 assert not n3.postprocessed
1022 assert n2.postprocessed
1023 assert n3.postprocessed
1025 def test_trace(self):
1026 """Test Taskmaster tracing
1030 trace = StringIO.StringIO()
1033 n3 = Node("n3", [n1, n2])
1034 tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1039 n1.set_state(SCons.Node.executed)
1044 n2.set_state(SCons.Node.executed)
1052 value = trace.getvalue()
1055 Taskmaster: Looking for a node to evaluate
1056 Taskmaster: Considering node <no_state 0 'n1'> and its children:
1057 Taskmaster: Evaluating <pending 0 'n1'>
1059 Taskmaster: Looking for a node to evaluate
1060 Taskmaster: Considering node <executed 0 'n1'> and its children:
1061 Taskmaster: already handled (executed)
1062 Taskmaster: Considering node <no_state 0 'n3'> and its children:
1063 Taskmaster: <executed 0 'n1'>
1064 Taskmaster: <no_state 0 'n2'>
1065 Taskmaster: adjusting ref count: <pending 1 'n3'>
1066 Taskmaster: Considering node <no_state 0 'n2'> and its children:
1067 Taskmaster: Evaluating <pending 0 'n2'>
1069 Taskmaster: Looking for a node to evaluate
1070 Taskmaster: Considering node <pending 0 'n3'> and its children:
1071 Taskmaster: <executed 0 'n1'>
1072 Taskmaster: <executed 0 'n2'>
1073 Taskmaster: Evaluating <pending 0 'n3'>
1075 Taskmaster: Looking for a node to evaluate
1076 Taskmaster: No candidate anymore.
1079 assert value == expect, value
1083 if __name__ == "__main__":
1084 suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1085 if not unittest.TextTestRunner().run(suite).wasSuccessful():