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
58 self.waiting_parents = {}
61 self.side_effects = []
63 self.postprocessed = None
67 def disambiguate(self):
70 def retrieve_from_cache(self):
73 cache_text.append(self.name + " retrieved")
78 built_text = self.name + " built"
80 def has_builder(self):
81 return not self.builder is None
84 return self.has_builder or self.side_effect
86 def alter_targets(self):
87 return self.alttargets, None
91 built_text = built_text + " really"
95 visited_nodes.append(self.name)
108 scan_called = scan_called + 1
109 self.kids = self.kids + self.scans
112 def scanner_key(self):
115 def add_to_waiting_parents(self, node):
116 wp = self.waiting_parents
124 def call_for_all_waiting_parents(self, func):
126 for parent in self.waiting_parents.keys():
127 parent.call_for_all_waiting_parents(func)
132 def set_state(self, state):
135 def set_bsig(self, bsig):
138 def set_csig(self, csig):
141 def store_csig(self):
144 def store_bsig(self):
147 def calculator(self):
149 def bsig(self, node):
150 return node._bsig_val
151 def current(self, node, sig):
152 return node._current_val
155 def current(self, calc=None):
157 calc = self.calculator()
158 return calc.current(self, calc.bsig(self))
160 def depends_on(self, nodes):
162 if node in self.kids:
169 def postprocess(self):
170 self.postprocessed = 1
172 def get_executor(self):
176 e.targets = self.targets
179 class OtherError(Exception):
182 class MyException(Exception):
186 class TaskmasterTestCase(unittest.TestCase):
188 def test_next_task(self):
189 """Test fetching the next task
194 tm = SCons.Taskmaster.Taskmaster([n1, n1])
203 n3 = Node("n3", [n1, n2])
205 tm = SCons.Taskmaster.Taskmaster([n3])
210 assert built_text == "n1 built", built_text
217 assert built_text == "n2 built", built_text
224 assert built_text == "n3 built", built_text
228 assert tm.next_task() == None
230 built_text = "up to date: "
233 class MyTask(SCons.Taskmaster.Task):
236 if self.targets[0].get_state() == SCons.Node.up_to_date:
238 built_text = self.targets[0].name + " up-to-date top"
240 built_text = self.targets[0].name + " up-to-date"
242 self.targets[0].build()
244 n1.set_state(SCons.Node.no_state)
246 n2.set_state(SCons.Node.no_state)
248 n3.set_state(SCons.Node.no_state)
250 tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
255 assert built_text == "n1 up-to-date", built_text
262 assert built_text == "n2 up-to-date", built_text
269 assert built_text == "n3 up-to-date top", built_text
273 assert tm.next_task() == None
278 n3 = Node("n3", [n1, n2])
280 n5 = Node("n5", [n3, n4])
281 tm = SCons.Taskmaster.Taskmaster([n5])
284 assert t1.get_target() == n1
287 assert t2.get_target() == n2
290 assert t4.get_target() == n4
299 assert t3.get_target() == n3
304 assert t5.get_target() == n5, t5.get_target()
308 assert tm.next_task() == None
312 n4.set_state(SCons.Node.executed)
313 tm = SCons.Taskmaster.Taskmaster([n4])
314 assert tm.next_task() == None
317 n2 = Node("n2", [n1])
318 tm = SCons.Taskmaster.Taskmaster([n2,n2])
323 assert tm.next_task() == None
328 n3 = Node("n3", [n1], [n2])
329 tm = SCons.Taskmaster.Taskmaster([n3])
331 target = t.get_target()
332 assert target == n1, target
336 target = t.get_target()
337 assert target == n2, target
341 target = t.get_target()
342 assert target == n3, target
345 assert tm.next_task() == None
349 n3 = Node("n3", [n1, n2])
350 n4 = Node("n4", [n3])
351 n5 = Node("n5", [n3])
354 tm = SCons.Taskmaster.Taskmaster([n4])
356 assert t.get_target() == n1
360 assert t.get_target() == n2
364 assert t.get_target() == n3
368 assert t.get_target() == n4
371 assert tm.next_task() == None
372 assert scan_called == 4, scan_called
374 tm = SCons.Taskmaster.Taskmaster([n5])
376 assert t.get_target() == n5, t.get_target()
378 assert tm.next_task() == None
379 assert scan_called == 5, scan_called
384 n4 = Node("n4", [n1,n2,n3])
385 n5 = Node("n5", [n4])
387 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
388 tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
390 assert t.get_target() == n1
391 assert n4.state == SCons.Node.executing, n4.state
395 assert t.get_target() == n2
399 assert t.get_target() == n3
403 assert t.get_target() == n4
407 assert t.get_target() == n5
408 assert not tm.next_task()
415 n4 = Node("n4", [n1,n2,n3])
416 def reverse(dependencies):
417 dependencies.reverse()
419 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
421 assert t.get_target() == n3, t.get_target()
425 assert t.get_target() == n2, t.get_target()
429 assert t.get_target() == n1, t.get_target()
433 assert t.get_target() == n4, t.get_target()
442 tm = SCons.Taskmaster.Taskmaster([n5])
444 assert t.get_target() == n5
448 tm = SCons.Taskmaster.Taskmaster([n6])
450 assert t.get_target() == n7
454 assert t.get_target() == n6
459 n2 = Node("n2", [n1])
460 n1.set_state(SCons.Node.failed)
461 tm = SCons.Taskmaster.Taskmaster([n2])
462 assert tm.next_task() is None
466 n1.targets = [n1, n2]
468 tm = SCons.Taskmaster.Taskmaster([n1])
474 assert s == SCons.Node.up_to_date, s
476 assert s == SCons.Node.executed, s
479 def test_make_ready_out_of_date(self):
480 """Test the Task.make_ready() method's list of out-of-date Nodes
483 def TaskGen(tm, targets, top, node, ood=ood):
484 class MyTask(SCons.Taskmaster.Task):
485 def make_ready(self):
486 SCons.Taskmaster.Task.make_ready(self)
487 self.ood.extend(self.out_of_date)
488 t = MyTask(tm, targets, top, node)
498 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
503 assert ood == [n1], ood
507 assert ood == [], ood
511 assert ood == [n3], ood
515 assert ood == [], 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 == [], 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'], 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 def test_execute(self):
815 """Test executing a task
821 tm = SCons.Taskmaster.Taskmaster([n1])
824 assert built_text == "n1 built", built_text
826 def raise_UserError():
827 raise SCons.Errors.UserError
829 n2.build = raise_UserError
830 tm = SCons.Taskmaster.Taskmaster([n2])
834 except SCons.Errors.UserError:
837 raise TestFailed, "did not catch expected UserError"
839 def raise_BuildError():
840 raise SCons.Errors.BuildError
842 n3.build = raise_BuildError
843 tm = SCons.Taskmaster.Taskmaster([n3])
847 except SCons.Errors.BuildError:
850 raise TestFailed, "did not catch expected BuildError"
852 # On a generic (non-BuildError) exception from a Builder,
853 # the target should throw a BuildError exception with the
854 # args set to the exception value, instance, and traceback.
855 def raise_OtherError():
858 n4.build = raise_OtherError
859 tm = SCons.Taskmaster.Taskmaster([n4])
863 except SCons.Errors.TaskmasterException, e:
864 assert e.node == n4, e.node
865 assert e.errstr == "Exception", e.errstr
866 assert len(e.exc_info) == 3, e.exc_info
867 exc_traceback = sys.exc_info()[2]
868 assert type(e.exc_info[2]) == type(exc_traceback), e.exc_info[2]
870 raise TestFailed, "did not catch expected BuildError"
877 tm = SCons.Taskmaster.Taskmaster([n5])
879 # This next line is moderately bogus. We're just reaching
880 # in and setting the targets for this task to an array. The
881 # "right" way to do this would be to have the next_task() call
882 # set it up by having something that approximates a real Builder
883 # return this list--but that's more work than is probably
884 # warranted right now.
887 assert built_text == "n5 built", built_text
888 assert cache_text == [], cache_text
896 tm = SCons.Taskmaster.Taskmaster([n7])
898 # This next line is moderately bogus. We're just reaching
899 # in and setting the targets for this task to an array. The
900 # "right" way to do this would be to have the next_task() call
901 # set it up by having something that approximates a real Builder
902 # return this list--but that's more work than is probably
903 # warranted right now.
906 assert built_text is None, built_text
907 assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
909 def test_exception(self):
910 """Test generic Taskmaster exception handling
914 tm = SCons.Taskmaster.Taskmaster([n1])
917 t.exception_set((1, 2))
918 exc_type, exc_value = t.exception
919 assert exc_type == 1, exc_type
920 assert exc_value == 2, exc_value
923 assert t.exception == 3
927 t.exception_set(None)
928 exc_type, exc_value, exc_tb = t.exception
929 assert exc_type is ZeroDivisionError, exc_type
931 "integer division or modulo",
932 "integer division or modulo by zero",
934 assert str(exc_value) in exception_values, exc_value
936 t.exception_set(("exception 1", None))
940 exc_type, exc_value = sys.exc_info()[:2]
941 assert exc_type == "exception 1", exc_type
942 assert exc_value is None, exc_value
944 assert 0, "did not catch expected exception"
946 t.exception_set(("exception 2", "xyzzy"))
950 exc_type, exc_value = sys.exc_info()[:2]
951 assert exc_type == "exception 2", exc_type
952 assert exc_value == "xyzzy", exc_value
954 assert 0, "did not catch expected exception"
959 tb = sys.exc_info()[2]
960 t.exception_set(("exception 3", "arg", tb))
964 exc_type, exc_value, exc_tb = sys.exc_info()
965 assert exc_type == 'exception 3', exc_type
966 assert exc_value == "arg", exc_value
968 x = traceback.extract_tb(tb)[-1]
969 y = traceback.extract_tb(exc_tb)[-1]
970 assert x == y, "x = %s, y = %s" % (x, y)
972 assert 0, "did not catch expected exception"
974 def test_postprocess(self):
975 """Test postprocessing targets to give them a chance to clean up
978 tm = SCons.Taskmaster.Taskmaster([n1])
981 assert not n1.postprocessed
983 assert n1.postprocessed
987 tm = SCons.Taskmaster.Taskmaster([n2, n3])
989 assert not n2.postprocessed
990 assert not n3.postprocessed
993 assert n2.postprocessed
994 assert not n3.postprocessed
997 assert n2.postprocessed
998 assert n3.postprocessed
1000 def test_trace(self):
1001 """Test Taskmaster tracing
1005 trace = StringIO.StringIO()
1008 n3 = Node("n3", [n1, n2])
1009 tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1013 n1.set_state(SCons.Node.executed)
1019 value = trace.getvalue()
1021 Taskmaster: 'n1': evaluating n1
1022 Taskmaster: 'n1': already handled (executed)
1023 Taskmaster: 'n3': children:
1025 waiting on unstarted children:
1027 Taskmaster: 'n2': evaluating n2
1028 Taskmaster: 'n3': children:
1030 waiting on unfinished children:
1033 assert value == expect, value
1037 if __name__ == "__main__":
1038 suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1039 if not unittest.TextTestRunner().run(suite).wasSuccessful():