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 = []):
50 def targets(self, node):
52 self.builder = Builder()
55 self.state = SCons.Node.no_state
57 self.waiting_parents = {}
60 self.side_effects = []
62 self.postprocessed = None
66 def disambiguate(self):
69 def retrieve_from_cache(self):
72 cache_text.append(self.name + " retrieved")
77 built_text = self.name + " built"
79 def has_builder(self):
80 return not self.builder is None
83 return self.has_builder or self.side_effect
85 def alter_targets(self):
86 return self.alttargets, None
90 built_text = built_text + " really"
94 visited_nodes.append(self.name)
107 scan_called = scan_called + 1
108 self.kids = self.kids + self.scans
111 def scanner_key(self):
114 def add_to_waiting_parents(self, node):
115 self.waiting_parents[node] = 1
117 def call_for_all_waiting_parents(self, func):
119 for parent in self.waiting_parents.keys():
120 parent.call_for_all_waiting_parents(func)
125 def set_state(self, state):
128 def set_bsig(self, bsig):
131 def set_csig(self, csig):
134 def store_csig(self):
137 def store_bsig(self):
140 def calculator(self):
142 def bsig(self, node):
143 return node._bsig_val
144 def current(self, node, sig):
145 return node._current_val
148 def current(self, calc=None):
150 calc = self.calculator()
151 return calc.current(self, calc.bsig(self))
153 def depends_on(self, nodes):
155 if node in self.kids:
162 def postprocess(self):
163 self.postprocessed = 1
165 def get_executor(self):
169 e.targets = self.targets
172 class OtherError(Exception):
175 class MyException(Exception):
179 class TaskmasterTestCase(unittest.TestCase):
181 def test_next_task(self):
182 """Test fetching the next task
187 tm = SCons.Taskmaster.Taskmaster([n1, n1])
196 n3 = Node("n3", [n1, n2])
198 tm = SCons.Taskmaster.Taskmaster([n3])
203 assert built_text == "n1 built", built_text
210 assert built_text == "n2 built", built_text
217 assert built_text == "n3 built", built_text
221 assert tm.next_task() == None
223 built_text = "up to date: "
226 class MyTask(SCons.Taskmaster.Task):
229 if self.targets[0].get_state() == SCons.Node.up_to_date:
231 built_text = self.targets[0].name + " up-to-date top"
233 built_text = self.targets[0].name + " up-to-date"
235 self.targets[0].build()
237 n1.set_state(SCons.Node.no_state)
239 n2.set_state(SCons.Node.no_state)
241 n3.set_state(SCons.Node.no_state)
243 tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
248 assert built_text == "n1 up-to-date", built_text
255 assert built_text == "n2 up-to-date", built_text
262 assert built_text == "n3 up-to-date top", built_text
266 assert tm.next_task() == None
271 n3 = Node("n3", [n1, n2])
273 n5 = Node("n5", [n3, n4])
274 tm = SCons.Taskmaster.Taskmaster([n5])
277 assert t1.get_target() == n1
280 assert t2.get_target() == n2
283 assert t4.get_target() == n4
292 assert t3.get_target() == n3
297 assert t5.get_target() == n5, t5.get_target()
301 assert tm.next_task() == None
305 n4.set_state(SCons.Node.executed)
306 tm = SCons.Taskmaster.Taskmaster([n4])
307 assert tm.next_task() == None
310 n2 = Node("n2", [n1])
311 tm = SCons.Taskmaster.Taskmaster([n2,n2])
316 assert tm.next_task() == None
321 n3 = Node("n3", [n1], [n2])
322 tm = SCons.Taskmaster.Taskmaster([n3])
324 target = t.get_target()
325 assert target == n1, target
329 target = t.get_target()
330 assert target == n2, target
334 target = t.get_target()
335 assert target == n3, target
338 assert tm.next_task() == None
342 n3 = Node("n3", [n1, n2])
343 n4 = Node("n4", [n3])
344 n5 = Node("n5", [n3])
347 tm = SCons.Taskmaster.Taskmaster([n4])
349 assert t.get_target() == n1
353 assert t.get_target() == n2
357 assert t.get_target() == n3
361 assert t.get_target() == n4
364 assert tm.next_task() == None
365 assert scan_called == 4, scan_called
367 tm = SCons.Taskmaster.Taskmaster([n5])
369 assert t.get_target() == n5, t.get_target()
371 assert tm.next_task() == None
372 assert scan_called == 5, scan_called
377 n4 = Node("n4", [n1,n2,n3])
378 n5 = Node("n5", [n4])
380 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
381 tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
383 assert t.get_target() == n1
384 assert n4.state == SCons.Node.executing, n4.state
388 assert t.get_target() == n2
392 assert t.get_target() == n3
396 assert t.get_target() == n4
400 assert t.get_target() == n5
401 assert not tm.next_task()
408 n4 = Node("n4", [n1,n2,n3])
409 def reverse(dependencies):
410 dependencies.reverse()
412 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
414 assert t.get_target() == n3, t.get_target()
418 assert t.get_target() == n2, t.get_target()
422 assert t.get_target() == n1, t.get_target()
426 assert t.get_target() == n4, t.get_target()
435 tm = SCons.Taskmaster.Taskmaster([n5])
437 assert t.get_target() == n5
441 tm = SCons.Taskmaster.Taskmaster([n6])
443 assert t.get_target() == n7
447 assert t.get_target() == n6
452 n2 = Node("n2", [n1])
453 n1.set_state(SCons.Node.failed)
454 tm = SCons.Taskmaster.Taskmaster([n2])
455 assert tm.next_task() is None
459 n1.targets = [n1, n2]
461 tm = SCons.Taskmaster.Taskmaster([n1])
467 assert s == SCons.Node.up_to_date, s
469 assert s == SCons.Node.executed, s
472 def test_make_ready_out_of_date(self):
473 """Test the Task.make_ready() method's list of out-of-date Nodes
476 def TaskGen(tm, targets, top, node, ood=ood):
477 class MyTask(SCons.Taskmaster.Task):
478 def make_ready(self):
479 SCons.Taskmaster.Task.make_ready(self)
480 self.ood.extend(self.out_of_date)
481 t = MyTask(tm, targets, top, node)
491 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
496 assert ood == [n1], ood
500 assert ood == [], ood
504 assert ood == [n3], ood
508 assert ood == [], ood
511 def test_make_ready_exception(self):
512 """Test handling exceptions from Task.make_ready()
514 class MyTask(SCons.Taskmaster.Task):
515 def make_ready(self):
516 raise MyException, "from make_ready()"
519 tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
521 exc_type, exc_value, exc_tb = t.exception
522 assert exc_type == MyException, repr(exc_type)
523 assert str(exc_value) == "from make_ready()", exc_value
526 def test_make_ready_all(self):
527 """Test the make_ready_all() method"""
528 class MyTask(SCons.Taskmaster.Task):
529 make_ready = SCons.Taskmaster.Task.make_ready_all
538 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
541 target = t.get_target()
542 assert target is n1, target
543 assert target.state == SCons.Node.executing, target.state
545 target = t.get_target()
546 assert target is c2, target
547 assert target.state == SCons.Node.up_to_date, target.state
549 target = t.get_target()
550 assert target is n3, target
551 assert target.state == SCons.Node.executing, target.state
553 target = t.get_target()
554 assert target is c4, target
555 assert target.state == SCons.Node.up_to_date, target.state
564 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
568 target = t.get_target()
569 assert target is n1, target
570 assert target.state == SCons.Node.executing, target.state
572 target = t.get_target()
573 assert target is c2, target
574 assert target.state == SCons.Node.executing, target.state
576 target = t.get_target()
577 assert target is n3, target
578 assert target.state == SCons.Node.executing, target.state
580 target = t.get_target()
581 assert target is c4, target
582 assert target.state == SCons.Node.executing, target.state
587 def test_children_errors(self):
588 """Test errors when fetching the children of a node.
590 class StopNode(Node):
592 raise SCons.Errors.StopError, "stop!"
593 class ExitNode(Node):
598 tm = SCons.Taskmaster.Taskmaster([n1])
600 exc_type, exc_value, exc_tb = t.exception
601 assert exc_type == SCons.Errors.StopError, repr(exc_type)
602 assert str(exc_value) == "stop!", exc_value
605 tm = SCons.Taskmaster.Taskmaster([n2])
607 exc_type, exc_value = t.exception
608 assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
609 assert exc_value.node == n2, exc_value.node
610 assert exc_value.status == 77, exc_value.status
612 def test_cycle_detection(self):
613 """Test detecting dependency cycles
616 n2 = Node("n2", [n1])
617 n3 = Node("n3", [n2])
620 tm = SCons.Taskmaster.Taskmaster([n3])
623 except SCons.Errors.UserError, e:
624 assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
626 assert 'Did not catch expected UserError'
628 def test_next_top_level_candidate(self):
629 """Test the next_top_level_candidate() method
632 n2 = Node("n2", [n1])
633 n3 = Node("n3", [n2])
635 tm = SCons.Taskmaster.Taskmaster([n3])
637 assert t.targets == [n1], t.targets
639 assert t.targets == [n3], map(str, t.targets)
640 assert t.top == 1, t.top
643 """Test the stop() method
645 Both default and overridden in a subclass.
651 n3 = Node("n3", [n1, n2])
653 tm = SCons.Taskmaster.Taskmaster([n3])
657 assert built_text == "n1 built", built_text
660 assert built_text == "n1 built really", built_text
663 assert tm.next_task() is None
665 class MyTM(SCons.Taskmaster.Taskmaster):
668 built_text = "MyTM.stop()"
669 SCons.Taskmaster.Taskmaster.stop(self)
673 n3 = Node("n3", [n1, n2])
677 tm.next_task().execute()
678 assert built_text == "n1 built"
681 assert built_text == "MyTM.stop()"
682 assert tm.next_task() is None
684 def test_executed(self):
685 """Test when a task has been executed
691 tm = SCons.Taskmaster.Taskmaster([n1])
695 n1.set_state(SCons.Node.executing)
700 assert s == SCons.Node.executed, s
701 assert built_text == "xxx really", built_text
702 assert visited_nodes == [], visited_nodes
705 tm = SCons.Taskmaster.Taskmaster([n2])
707 built_text = "should_not_change"
715 assert built_text == "should_not_change", built_text
716 assert visited_nodes == ['n2'], visited_nodes
720 n3.targets = [n3, n4]
721 tm = SCons.Taskmaster.Taskmaster([n3])
724 n3.set_state(SCons.Node.up_to_date)
725 n4.set_state(SCons.Node.executing)
730 assert s == SCons.Node.up_to_date, s
732 assert s == SCons.Node.executed, s
733 assert visited_nodes == ['n3'], visited_nodes
735 def test_prepare(self):
736 """Test preparation of multiple Nodes for a task
740 tm = SCons.Taskmaster.Taskmaster([n1, n2])
742 # This next line is moderately bogus. We're just reaching
743 # in and setting the targets for this task to an array. The
744 # "right" way to do this would be to have the next_task() call
745 # set it up by having something that approximates a real Builder
746 # return this list--but that's more work than is probably
747 # warranted right now.
755 tm = SCons.Taskmaster.Taskmaster([n3, n4])
757 # More bogus reaching in and setting the targets.
758 n3.set_state(SCons.Node.up_to_date)
764 # If the Node has had an exception recorded while it was getting
765 # prepared, then prepare() should raise that exception.
766 class MyException(Exception):
771 tm = SCons.Taskmaster.Taskmaster([n5])
773 t.exception_set((MyException, "exception value"))
777 except MyException, e:
781 assert exc_caught, "did not catch expected MyException"
782 assert str(e) == "exception value", e
783 assert built_text is None, built_text
785 # Regression test, make sure we prepare not only
786 # all targets, but their side effects as well.
793 n6.side_effects = [ n8 ]
794 n7.side_effects = [ n9, n10 ]
796 tm = SCons.Taskmaster.Taskmaster([n6, n7])
798 # More bogus reaching in and setting the targets.
807 def test_execute(self):
808 """Test executing a task
814 tm = SCons.Taskmaster.Taskmaster([n1])
817 assert built_text == "n1 built", built_text
819 def raise_UserError():
820 raise SCons.Errors.UserError
822 n2.build = raise_UserError
823 tm = SCons.Taskmaster.Taskmaster([n2])
827 except SCons.Errors.UserError:
830 raise TestFailed, "did not catch expected UserError"
832 def raise_BuildError():
833 raise SCons.Errors.BuildError
835 n3.build = raise_BuildError
836 tm = SCons.Taskmaster.Taskmaster([n3])
840 except SCons.Errors.BuildError:
843 raise TestFailed, "did not catch expected BuildError"
845 # On a generic (non-BuildError) exception from a Builder,
846 # the target should throw a BuildError exception with the
847 # args set to the exception value, instance, and traceback.
848 def raise_OtherError():
851 n4.build = raise_OtherError
852 tm = SCons.Taskmaster.Taskmaster([n4])
856 except SCons.Errors.TaskmasterException, e:
857 assert e.node == n4, e.node
858 assert e.errstr == "Exception", e.errstr
859 assert len(e.exc_info) == 3, e.exc_info
860 exc_traceback = sys.exc_info()[2]
861 assert type(e.exc_info[2]) == type(exc_traceback), e.exc_info[2]
863 raise TestFailed, "did not catch expected BuildError"
870 tm = SCons.Taskmaster.Taskmaster([n5])
872 # This next line is moderately bogus. We're just reaching
873 # in and setting the targets for this task to an array. The
874 # "right" way to do this would be to have the next_task() call
875 # set it up by having something that approximates a real Builder
876 # return this list--but that's more work than is probably
877 # warranted right now.
880 assert built_text == "n5 built", built_text
881 assert cache_text == [], cache_text
889 tm = SCons.Taskmaster.Taskmaster([n7])
891 # This next line is moderately bogus. We're just reaching
892 # in and setting the targets for this task to an array. The
893 # "right" way to do this would be to have the next_task() call
894 # set it up by having something that approximates a real Builder
895 # return this list--but that's more work than is probably
896 # warranted right now.
899 assert built_text is None, built_text
900 assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
902 def test_exception(self):
903 """Test generic Taskmaster exception handling
907 tm = SCons.Taskmaster.Taskmaster([n1])
910 t.exception_set((1, 2))
911 exc_type, exc_value = t.exception
912 assert exc_type == 1, exc_type
913 assert exc_value == 2, exc_value
916 assert t.exception == 3
920 t.exception_set(None)
921 exc_type, exc_value, exc_tb = t.exception
922 assert exc_type is ZeroDivisionError, exc_type
924 "integer division or modulo",
925 "integer division or modulo by zero",
927 assert str(exc_value) in exception_values, exc_value
929 t.exception_set(("exception 1", None))
933 exc_type, exc_value = sys.exc_info()[:2]
934 assert exc_type == "exception 1", exc_type
935 assert exc_value is None, exc_value
937 assert 0, "did not catch expected exception"
939 t.exception_set(("exception 2", "xyzzy"))
943 exc_type, exc_value = sys.exc_info()[:2]
944 assert exc_type == "exception 2", exc_type
945 assert exc_value == "xyzzy", exc_value
947 assert 0, "did not catch expected exception"
952 tb = sys.exc_info()[2]
953 t.exception_set(("exception 3", "arg", tb))
957 exc_type, exc_value, exc_tb = sys.exc_info()
958 assert exc_type == 'exception 3', exc_type
959 assert exc_value == "arg", exc_value
961 x = traceback.extract_tb(tb)[-1]
962 y = traceback.extract_tb(exc_tb)[-1]
963 assert x == y, "x = %s, y = %s" % (x, y)
965 assert 0, "did not catch expected exception"
967 def test_postprocess(self):
968 """Test postprocessing targets to give them a chance to clean up
971 tm = SCons.Taskmaster.Taskmaster([n1])
974 assert not n1.postprocessed
976 assert n1.postprocessed
980 tm = SCons.Taskmaster.Taskmaster([n2, n3])
982 assert not n2.postprocessed
983 assert not n3.postprocessed
986 assert n2.postprocessed
987 assert not n3.postprocessed
990 assert n2.postprocessed
991 assert n3.postprocessed
993 def test_trace(self):
994 """Test Taskmaster tracing
998 trace = StringIO.StringIO()
1001 n3 = Node("n3", [n1, n2])
1002 tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1006 n1.set_state(SCons.Node.executed)
1012 value = trace.getvalue()
1014 Taskmaster: 'n1': evaluating n1
1015 Taskmaster: 'n1': already handled (executed)
1016 Taskmaster: 'n3': children:
1018 waiting on unstarted children:
1020 Taskmaster: 'n2': evaluating n2
1021 Taskmaster: 'n3': children:
1023 waiting on unfinished children:
1026 assert value == expect, value
1030 if __name__ == "__main__":
1031 suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1032 if not unittest.TextTestRunner().run(suite).wasSuccessful():