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 = []
59 self.side_effects = []
61 self.postprocessed = None
65 def disambiguate(self):
68 def retrieve_from_cache(self):
71 cache_text.append(self.name + " retrieved")
76 built_text = self.name + " built"
78 def has_builder(self):
79 return not self.builder is None
82 return self.has_builder or self.side_effect
84 def alter_targets(self):
85 return self.alttargets, None
89 built_text = built_text + " really"
93 visited_nodes.append(self.name)
106 scan_called = scan_called + 1
107 self.kids = self.kids + self.scans
110 def scanner_key(self):
113 def add_to_waiting_parents(self, node):
114 self.waiting_parents.append(node)
116 def call_for_all_waiting_parents(self, func):
118 for parent in self.waiting_parents:
119 parent.call_for_all_waiting_parents(func)
124 def set_state(self, state):
127 def set_bsig(self, bsig):
130 def set_csig(self, csig):
133 def store_csig(self):
136 def store_bsig(self):
139 def calculator(self):
141 def bsig(self, node):
142 return node._bsig_val
143 def current(self, node, sig):
144 return node._current_val
147 def current(self, calc=None):
149 calc = self.calculator()
150 return calc.current(self, calc.bsig(self))
152 def depends_on(self, nodes):
154 if node in self.kids:
161 def postprocess(self):
162 self.postprocessed = 1
164 class OtherError(Exception):
167 class MyException(Exception):
171 class TaskmasterTestCase(unittest.TestCase):
173 def test_next_task(self):
174 """Test fetching the next task
179 tm = SCons.Taskmaster.Taskmaster([n1, n1])
188 n3 = Node("n3", [n1, n2])
190 tm = SCons.Taskmaster.Taskmaster([n3])
195 assert built_text == "n1 built", built_text
201 assert built_text == "n2 built", built_text
207 assert built_text == "n3 built", built_text
210 assert tm.next_task() == None
212 built_text = "up to date: "
215 class MyTask(SCons.Taskmaster.Task):
218 if self.targets[0].get_state() == SCons.Node.up_to_date:
220 built_text = self.targets[0].name + " up-to-date top"
222 built_text = self.targets[0].name + " up-to-date"
224 self.targets[0].build()
226 n1.set_state(SCons.Node.no_state)
228 n2.set_state(SCons.Node.no_state)
230 n3.set_state(SCons.Node.no_state)
232 tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
237 assert built_text == "n1 up-to-date", built_text
243 assert built_text == "n2 up-to-date", built_text
249 assert built_text == "n3 up-to-date top", built_text
252 assert tm.next_task() == None
257 n3 = Node("n3", [n1, n2])
259 n5 = Node("n5", [n3, n4])
260 tm = SCons.Taskmaster.Taskmaster([n5])
262 assert not tm.is_blocked()
265 assert t1.get_target() == n1
266 assert not tm.is_blocked()
269 assert t2.get_target() == n2
270 assert not tm.is_blocked()
273 assert t4.get_target() == n4
274 assert tm.is_blocked()
276 assert tm.is_blocked()
279 assert tm.is_blocked()
281 assert not tm.is_blocked()
283 assert t3.get_target() == n3
284 assert tm.is_blocked()
287 assert not tm.is_blocked()
289 assert t5.get_target() == n5, t5.get_target()
290 assert tm.is_blocked() # still executing t5
292 assert not tm.is_blocked()
294 assert tm.next_task() == None
298 n4.set_state(SCons.Node.executed)
299 tm = SCons.Taskmaster.Taskmaster([n4])
300 assert tm.next_task() == None
303 n2 = Node("n2", [n1])
304 tm = SCons.Taskmaster.Taskmaster([n2,n2])
306 assert tm.is_blocked()
308 assert not tm.is_blocked()
310 assert tm.next_task() == None
315 n3 = Node("n3", [n1], [n2])
316 tm = SCons.Taskmaster.Taskmaster([n3])
318 target = t.get_target()
319 assert target == n1, target
322 target = t.get_target()
323 assert target == n2, target
326 target = t.get_target()
327 assert target == n3, target
329 assert tm.next_task() == None
333 n3 = Node("n3", [n1, n2])
334 n4 = Node("n4", [n3])
335 n5 = Node("n5", [n3])
338 tm = SCons.Taskmaster.Taskmaster([n4])
340 assert t.get_target() == n1
343 assert t.get_target() == n2
346 assert t.get_target() == n3
349 assert t.get_target() == n4
351 assert tm.next_task() == None
352 assert scan_called == 4, scan_called
354 tm = SCons.Taskmaster.Taskmaster([n5])
356 assert t.get_target() == n5, t.get_target()
358 assert tm.next_task() == None
359 assert scan_called == 5, scan_called
364 n4 = Node("n4", [n1,n2,n3])
365 n5 = Node("n5", [n4])
367 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
368 tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
370 assert t.get_target() == n1
371 assert n4.state == SCons.Node.executing
372 assert tm.is_blocked()
374 assert not tm.is_blocked()
376 assert t.get_target() == n2
377 assert tm.is_blocked()
380 assert t.get_target() == n3
381 assert tm.is_blocked()
384 assert t.get_target() == n4
385 assert tm.is_blocked()
388 assert t.get_target() == n5
389 assert tm.is_blocked() # still executing n5
390 assert not tm.next_task()
392 assert not tm.is_blocked()
397 n4 = Node("n4", [n1,n2,n3])
398 def reverse(dependencies):
399 dependencies.reverse()
401 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
403 assert t.get_target() == n3, t.get_target()
406 assert t.get_target() == n2, t.get_target()
409 assert t.get_target() == n1, t.get_target()
412 assert t.get_target() == n4, t.get_target()
419 tm = SCons.Taskmaster.Taskmaster([n5])
421 assert t.get_target() == n5
423 tm = SCons.Taskmaster.Taskmaster([n6])
425 assert t.get_target() == n7
428 assert t.get_target() == n6
432 n2 = Node("n2", [n1])
433 n1.set_state(SCons.Node.failed)
434 tm = SCons.Taskmaster.Taskmaster([n2])
435 assert tm.next_task() is None
439 n1.targets = [n1, n2]
441 tm = SCons.Taskmaster.Taskmaster([n1])
446 assert s == SCons.Node.up_to_date, s
448 assert s == SCons.Node.executed, s
451 def test_make_ready_out_of_date(self):
452 """Test the Task.make_ready() method's list of out-of-date Nodes
455 def TaskGen(tm, targets, top, node, ood=ood):
456 class MyTask(SCons.Taskmaster.Task):
457 def make_ready(self):
458 SCons.Taskmaster.Task.make_ready(self)
459 self.ood.extend(self.out_of_date)
460 t = MyTask(tm, targets, top, node)
470 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
475 assert ood == [n1], ood
479 assert ood == [], ood
483 assert ood == [n3], ood
487 assert ood == [], ood
490 def test_make_ready_exception(self):
491 """Test handling exceptions from Task.make_ready()
493 class MyTask(SCons.Taskmaster.Task):
494 def make_ready(self):
495 raise MyException, "from make_ready()"
498 tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
500 exc_type, exc_value, exc_tb = t.exception
501 assert exc_type == MyException, repr(exc_type)
502 assert str(exc_value) == "from make_ready()", exc_value
505 def test_make_ready_all(self):
506 """Test the make_ready_all() method"""
507 class MyTask(SCons.Taskmaster.Task):
508 make_ready = SCons.Taskmaster.Task.make_ready_all
517 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
520 target = t.get_target()
521 assert target is n1, target
522 assert target.state == SCons.Node.executing, target.state
524 target = t.get_target()
525 assert target is c2, target
526 assert target.state == SCons.Node.up_to_date, target.state
528 target = t.get_target()
529 assert target is n3, target
530 assert target.state == SCons.Node.executing, target.state
532 target = t.get_target()
533 assert target is c4, target
534 assert target.state == SCons.Node.up_to_date, target.state
543 tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
547 target = t.get_target()
548 assert target is n1, target
549 assert target.state == SCons.Node.executing, target.state
551 target = t.get_target()
552 assert target is c2, target
553 assert target.state == SCons.Node.executing, target.state
555 target = t.get_target()
556 assert target is n3, target
557 assert target.state == SCons.Node.executing, target.state
559 target = t.get_target()
560 assert target is c4, target
561 assert target.state == SCons.Node.executing, target.state
566 def test_children_errors(self):
567 """Test errors when fetching the children of a node.
569 class StopNode(Node):
571 raise SCons.Errors.StopError, "stop!"
572 class ExitNode(Node):
577 tm = SCons.Taskmaster.Taskmaster([n1])
579 exc_type, exc_value, exc_tb = t.exception
580 assert exc_type == SCons.Errors.StopError, repr(exc_type)
581 assert str(exc_value) == "stop!", exc_value
584 tm = SCons.Taskmaster.Taskmaster([n2])
586 exc_type, exc_value = t.exception
587 assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
588 assert exc_value.node == n2, exc_value.node
589 assert exc_value.status == 77, exc_value.status
591 def test_cycle_detection(self):
592 """Test detecting dependency cycles
596 n2 = Node("n2", [n1])
597 n3 = Node("n3", [n2])
601 tm = SCons.Taskmaster.Taskmaster([n3])
603 except SCons.Errors.UserError, e:
604 assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
608 def test_is_blocked(self):
609 """Test whether a task is blocked
611 Both default and overridden in a subclass.
613 tm = SCons.Taskmaster.Taskmaster()
614 assert not tm.is_blocked()
616 class MyTM(SCons.Taskmaster.Taskmaster):
617 def _find_next_ready_node(self):
620 assert not tm.is_blocked()
622 class MyTM(SCons.Taskmaster.Taskmaster):
623 def _find_next_ready_node(self):
628 assert not tm.is_blocked()
630 class MyTM(SCons.Taskmaster.Taskmaster):
631 def _find_next_ready_node(self):
635 assert tm.is_blocked()
637 class MyTM(SCons.Taskmaster.Taskmaster):
638 def _find_next_ready_node(self):
642 assert tm.is_blocked()
644 def test_next_top_level_candidate(self):
645 """Test the next_top_level_candidate() method
648 n2 = Node("n2", [n1])
649 n3 = Node("n3", [n2])
651 tm = SCons.Taskmaster.Taskmaster([n3])
653 assert tm.executing == [n1], tm.executing
655 assert t.targets == [n3], t.targets
656 assert t.top == 1, t.top
659 """Test the stop() method
661 Both default and overridden in a subclass.
667 n3 = Node("n3", [n1, n2])
669 tm = SCons.Taskmaster.Taskmaster([n3])
673 assert built_text == "n1 built", built_text
675 assert built_text == "n1 built really", built_text
678 assert tm.next_task() is None
680 class MyTM(SCons.Taskmaster.Taskmaster):
683 built_text = "MyTM.stop()"
684 SCons.Taskmaster.Taskmaster.stop(self)
688 n3 = Node("n3", [n1, n2])
692 tm.next_task().execute()
693 assert built_text == "n1 built"
696 assert built_text == "MyTM.stop()"
697 assert tm.next_task() is None
699 def test_failed(self):
700 """Test when a task has failed
703 tm = SCons.Taskmaster.Taskmaster([n1])
705 assert tm.executing == [n1], tm.executing
707 assert tm.executing == [], tm.executing
709 def test_executed(self):
710 """Test when a task has been executed
716 tm = SCons.Taskmaster.Taskmaster([n1])
720 n1.set_state(SCons.Node.executing)
725 assert s == SCons.Node.executed, s
726 assert built_text == "xxx really", built_text
727 assert visited_nodes == [], visited_nodes
730 tm = SCons.Taskmaster.Taskmaster([n2])
732 built_text = "should_not_change"
740 assert built_text == "should_not_change", built_text
741 assert visited_nodes == ['n2'], visited_nodes
745 n3.targets = [n3, n4]
746 tm = SCons.Taskmaster.Taskmaster([n3])
749 n3.set_state(SCons.Node.up_to_date)
750 n4.set_state(SCons.Node.executing)
755 assert s == SCons.Node.up_to_date, s
757 assert s == SCons.Node.executed, s
758 assert visited_nodes == ['n3'], visited_nodes
760 def test_prepare(self):
761 """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.
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)
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.
833 def test_execute(self):
834 """Test executing a task
841 tm = SCons.Taskmaster.Taskmaster([n1])
844 assert built_text == "n1 built", built_text
846 def raise_UserError():
847 raise SCons.Errors.UserError
849 n2.build = raise_UserError
850 tm = SCons.Taskmaster.Taskmaster([n2])
854 except SCons.Errors.UserError:
857 raise TestFailed, "did not catch expected UserError"
859 def raise_BuildError():
860 raise SCons.Errors.BuildError
862 n3.build = raise_BuildError
863 tm = SCons.Taskmaster.Taskmaster([n3])
867 except SCons.Errors.BuildError:
870 raise TestFailed, "did not catch expected BuildError"
872 # On a generic (non-BuildError) exception from a Builder,
873 # the target should throw a BuildError exception with the
874 # args set to the exception value, instance, and traceback.
875 def raise_OtherError():
878 n4.build = raise_OtherError
879 tm = SCons.Taskmaster.Taskmaster([n4])
883 except SCons.Errors.BuildError, e:
884 assert e.node == n4, e.node
885 assert e.errstr == "Exception", e.errstr
886 assert len(e.args) == 3, `e.args`
887 assert e.args[0] == OtherError, e.args[0]
888 assert isinstance(e.args[1], OtherError), type(e.args[1])
889 exc_traceback = sys.exc_info()[2]
890 assert type(e.args[2]) == type(exc_traceback), e.args[2]
892 raise TestFailed, "did not catch expected BuildError"
899 tm = SCons.Taskmaster.Taskmaster([n5])
901 # This next line is moderately bogus. We're just reaching
902 # in and setting the targets for this task to an array. The
903 # "right" way to do this would be to have the next_task() call
904 # set it up by having something that approximates a real Builder
905 # return this list--but that's more work than is probably
906 # warranted right now.
909 assert built_text == "n5 built", built_text
910 assert cache_text == [], cache_text
918 tm = SCons.Taskmaster.Taskmaster([n7])
920 # This next line is moderately bogus. We're just reaching
921 # in and setting the targets for this task to an array. The
922 # "right" way to do this would be to have the next_task() call
923 # set it up by having something that approximates a real Builder
924 # return this list--but that's more work than is probably
925 # warranted right now.
928 assert built_text is None, built_text
929 assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
931 def test_exception(self):
932 """Test generic Taskmaster exception handling
936 tm = SCons.Taskmaster.Taskmaster([n1])
939 t.exception_set((1, 2))
940 exc_type, exc_value = t.exception
941 assert exc_type == 1, exc_type
942 assert exc_value == 2, exc_value
945 assert t.exception == 3
949 t.exception_set(None)
950 exc_type, exc_value, exc_tb = t.exception
951 assert exc_type is ZeroDivisionError, exc_type
953 "integer division or modulo",
954 "integer division or modulo by zero",
956 assert str(exc_value) in exception_values, exc_value
958 t.exception_set(("exception 1", None))
962 exc_type, exc_value = sys.exc_info()[:2]
963 assert exc_type == "exception 1", exc_type
964 assert exc_value is None, exc_value
966 assert 0, "did not catch expected exception"
968 t.exception_set(("exception 2", "xyzzy"))
972 exc_type, exc_value = sys.exc_info()[:2]
973 assert exc_type == "exception 2", exc_type
974 assert exc_value == "xyzzy", exc_value
976 assert 0, "did not catch expected exception"
981 tb = sys.exc_info()[2]
982 t.exception_set(("exception 3", "arg", tb))
986 exc_type, exc_value, exc_tb = sys.exc_info()
987 assert exc_type == 'exception 3', exc_type
988 assert exc_value == "arg", exc_value
990 x = traceback.extract_tb(tb)[-1]
991 y = traceback.extract_tb(exc_tb)[-1]
992 assert x == y, "x = %s, y = %s" % (x, y)
994 assert 0, "did not catch expected exception"
996 t.exception_set(("exception 4", "XYZZY"))
998 raise 'exception_forwarded', exc
999 tm.exception_raise = fw_exc
1003 exc_type, exc_value = sys.exc_info()[:2]
1004 assert exc_type == 'exception_forwarded', exc_type
1005 assert exc_value[0] == "exception 4", exc_value[0]
1006 assert exc_value[1] == "XYZZY", exc_value[1]
1008 assert 0, "did not catch expected exception"
1010 def test_postprocess(self):
1011 """Test postprocessing targets to give them a chance to clean up
1015 tm = SCons.Taskmaster.Taskmaster([n1])
1018 assert not n1.postprocessed
1020 assert n1.postprocessed
1024 tm = SCons.Taskmaster.Taskmaster([n2, n3])
1026 assert not n2.postprocessed
1027 assert not n3.postprocessed
1030 assert n2.postprocessed
1031 assert not n3.postprocessed
1034 assert n2.postprocessed
1035 assert n3.postprocessed
1037 def test_trace(self):
1038 """Test Taskmaster tracing
1042 trace = StringIO.StringIO()
1045 n3 = Node("n3", [n1, n2])
1046 tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1050 n1.set_state(SCons.Node.executed)
1056 value = trace.getvalue()
1058 Taskmaster: 'n1': children:
1061 Taskmaster: 'n1': already handled
1062 Taskmaster: 'n3': children:
1064 waiting on unstarted children:
1066 Taskmaster: 'n2': children:
1069 Taskmaster: 'n3': children:
1071 waiting on unfinished children:
1074 assert value == expect, value
1078 if __name__ == "__main__":
1079 suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1080 if not unittest.TextTestRunner().run(suite).wasSuccessful():