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__"
29 import SCons.Taskmaster
39 def __init__(self, name, kids = [], scans = []):
45 self.builder = Node.build
52 self.side_effects = []
56 kid.parents.append(self)
60 built_text = self.name + " built"
62 def has_builder(self):
63 return not self.builder is None
66 return self.has_builder or self.side_effect
68 def alter_targets(self):
69 return self.alttargets, None
73 built_text = built_text + " really"
77 visited_nodes.append(self.name)
90 scan_called = scan_called + 1
91 self.kids = self.kids + self.scans
92 for scan in self.scans:
93 scan.parents.append(self)
96 def scanner_key(self):
99 def get_parents(self):
105 def set_state(self, state):
108 def set_bsig(self, bsig):
111 def set_csig(self, csig):
114 def store_csig(self):
117 def store_bsig(self):
120 def current(self, calc):
121 return calc.current(self, calc.bsig(self))
123 def depends_on(self, nodes):
125 if node in self.kids:
133 class OtherError(Exception):
136 class MyException(Exception):
140 class TaskmasterTestCase(unittest.TestCase):
142 def test_next_task(self):
143 """Test fetching the next task
148 tm = SCons.Taskmaster.Taskmaster([n1, n1])
157 n3 = Node("n3", [n1, n2])
159 tm = SCons.Taskmaster.Taskmaster([n3])
164 assert built_text == "n1 built", built_text
170 assert built_text == "n2 built", built_text
176 assert built_text == "n3 built", built_text
179 assert tm.next_task() == None
181 built_text = "up to date: "
184 class MyCalc(SCons.Taskmaster.Calc):
185 def current(self, node, sig):
188 class MyTask(SCons.Taskmaster.Task):
191 if self.targets[0].get_state() == SCons.Node.up_to_date:
193 built_text = self.targets[0].name + " up-to-date top"
195 built_text = self.targets[0].name + " up-to-date"
197 self.targets[0].build()
202 tm = SCons.Taskmaster.Taskmaster(targets = [n3],
203 tasker = MyTask, calc = MyCalc())
208 assert built_text == "n1 up-to-date", built_text
214 assert built_text == "n2 up-to-date", built_text
220 assert built_text == "n3 up-to-date top", built_text
223 assert tm.next_task() == None
228 n3 = Node("n3", [n1, n2])
230 n5 = Node("n5", [n3, n4])
231 tm = SCons.Taskmaster.Taskmaster([n5])
233 assert not tm.is_blocked()
236 assert t1.get_target() == n1
237 assert not tm.is_blocked()
240 assert t2.get_target() == n2
241 assert not tm.is_blocked()
244 assert t4.get_target() == n4
245 assert tm.is_blocked()
247 assert tm.is_blocked()
250 assert tm.is_blocked()
252 assert not tm.is_blocked()
254 assert t3.get_target() == n3
255 assert tm.is_blocked()
258 assert not tm.is_blocked()
260 assert t5.get_target() == n5, t5.get_target()
261 assert tm.is_blocked() # still executing t5
263 assert not tm.is_blocked()
265 assert tm.next_task() == None
269 n4.set_state(SCons.Node.executed)
270 tm = SCons.Taskmaster.Taskmaster([n4])
271 assert tm.next_task() == None
274 n2 = Node("n2", [n1])
275 tm = SCons.Taskmaster.Taskmaster([n2,n2])
277 assert tm.is_blocked()
279 assert not tm.is_blocked()
281 assert tm.next_task() == None
286 n3 = Node("n3", [n1], [n2])
287 tm = SCons.Taskmaster.Taskmaster([n3])
289 target = t.get_target()
290 assert target == n1, target
293 target = t.get_target()
294 assert target == n2, target
297 target = t.get_target()
298 assert target == n3, target
300 assert tm.next_task() == None
304 n3 = Node("n3", [n1, n2])
305 n4 = Node("n4", [n3])
306 n5 = Node("n5", [n3])
309 tm = SCons.Taskmaster.Taskmaster([n4])
311 assert t.get_target() == n1
314 assert t.get_target() == n2
317 assert t.get_target() == n3
320 assert t.get_target() == n4
322 assert tm.next_task() == None
323 assert scan_called == 4, scan_called
325 tm = SCons.Taskmaster.Taskmaster([n5])
327 assert t.get_target() == n5, t.get_target()
329 assert tm.next_task() == None
330 assert scan_called == 5, scan_called
335 n4 = Node("n4", [n1,n2,n3])
336 n5 = Node("n5", [n4])
338 n1.side_effects = n2.side_effects = n3.side_effects = [n4]
339 tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
341 assert t.get_target() == n1
342 assert n4.state == SCons.Node.executing
343 assert tm.is_blocked()
345 assert not tm.is_blocked()
347 assert t.get_target() == n2
348 assert tm.is_blocked()
351 assert t.get_target() == n3
352 assert tm.is_blocked()
355 assert t.get_target() == n4
356 assert tm.is_blocked()
359 assert t.get_target() == n5
360 assert tm.is_blocked() # still executing n5
361 assert not tm.next_task()
363 assert not tm.is_blocked()
368 n4 = Node("n4", [n1,n2,n3])
369 def reverse(dependencies):
370 dependencies.reverse()
372 tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
374 assert t.get_target() == n3, t.get_target()
377 assert t.get_target() == n2, t.get_target()
380 assert t.get_target() == n1, t.get_target()
383 assert t.get_target() == n4, t.get_target()
390 tm = SCons.Taskmaster.Taskmaster([n5])
392 assert t.get_target() == n5
394 tm = SCons.Taskmaster.Taskmaster([n6])
396 assert t.get_target() == n7
400 def test_make_ready_exception(self):
401 """Test handling exceptions from Task.make_ready()
403 class MyTask(SCons.Taskmaster.Task):
404 def make_ready(self):
405 raise MyException, "from make_ready()"
408 tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
410 assert isinstance(tm.exc_type, SCons.Errors.TaskmasterException), repr(tm.exc_type)
411 assert tm.exc_value is None, tm.exc_value
413 assert e.type == MyException, e.type
414 assert str(e.value) == "from make_ready()", str(e.value)
417 def test_children_errors(self):
418 """Test errors when fetching the children of a node.
420 class StopNode(Node):
422 raise SCons.Errors.StopError, "stop!"
423 class ExitNode(Node):
428 tm = SCons.Taskmaster.Taskmaster([n1])
430 assert isinstance(tm.exc_type, SCons.Errors.TaskmasterException), repr(tm.exc_type)
431 assert tm.exc_value is None, tm.exc_value
433 assert e.type == SCons.Errors.StopError, e.type
434 assert str(e.value) == "stop!", "Unexpected exc_value `%s'" % e.value
437 tm = SCons.Taskmaster.Taskmaster([n2])
439 assert tm.exc_type == SCons.Errors.ExplicitExit, "Did not record ExplicitExit on node"
440 assert tm.exc_value.node == n2, tm.exc_value.node
441 assert tm.exc_value.status == 77, tm.exc_value.status
443 def test_cycle_detection(self):
444 """Test detecting dependency cycles
448 n2 = Node("n2", [n1])
449 n3 = Node("n3", [n2])
451 n3.parents.append(n1)
454 tm = SCons.Taskmaster.Taskmaster([n3])
456 except SCons.Errors.UserError, e:
457 assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
461 def test_is_blocked(self):
462 """Test whether a task is blocked
464 Both default and overridden in a subclass.
466 tm = SCons.Taskmaster.Taskmaster()
467 assert not tm.is_blocked()
469 class MyTM(SCons.Taskmaster.Taskmaster):
470 def _find_next_ready_node(self):
473 assert not tm.is_blocked()
475 class MyTM(SCons.Taskmaster.Taskmaster):
476 def _find_next_ready_node(self):
481 assert not tm.is_blocked()
483 class MyTM(SCons.Taskmaster.Taskmaster):
484 def _find_next_ready_node(self):
488 assert tm.is_blocked()
490 class MyTM(SCons.Taskmaster.Taskmaster):
491 def _find_next_ready_node(self):
495 assert tm.is_blocked()
498 """Test the stop() method
500 Both default and overridden in a subclass.
506 n3 = Node("n3", [n1, n2])
508 tm = SCons.Taskmaster.Taskmaster([n3])
512 assert built_text == "n1 built", built_text
514 assert built_text == "n1 built really", built_text
517 assert tm.next_task() is None
519 class MyTM(SCons.Taskmaster.Taskmaster):
522 built_text = "MyTM.stop()"
523 SCons.Taskmaster.Taskmaster.stop(self)
527 n3 = Node("n3", [n1, n2])
531 tm.next_task().execute()
532 assert built_text == "n1 built"
535 assert built_text == "MyTM.stop()"
536 assert tm.next_task() is None
538 def test_failed(self):
539 """Test when a task has failed
542 tm = SCons.Taskmaster.Taskmaster([n1])
544 assert tm.executing == [n1], tm.executing
546 assert tm.executing == [], tm.executing
548 def test_executed(self):
549 """Test when a task has been executed
555 tm = SCons.Taskmaster.Taskmaster([n1])
559 n1.set_state(SCons.Node.executing)
562 assert s == SCons.Node.executed, s
563 assert built_text == "xxx really", built_text
564 assert visited_nodes == [], visited_nodes
567 tm = SCons.Taskmaster.Taskmaster([n2])
569 built_text = "should_not_change"
574 assert s == SCons.Node.executed, s
575 assert built_text == "should_not_change", built_text
576 assert visited_nodes == ["n2"], visited_nodes
578 def test_prepare(self):
579 """Test preparation of multiple Nodes for a task
584 tm = SCons.Taskmaster.Taskmaster([n1, n2])
586 # This next line is moderately bogus. We're just reaching
587 # in and setting the targets for this task to an array. The
588 # "right" way to do this would be to have the next_task() call
589 # set it up by having something that approximates a real Builder
590 # return this list--but that's more work than is probably
591 # warranted right now.
599 tm = SCons.Taskmaster.Taskmaster([n3, n4])
601 # More bogus reaching in and setting the targets.
602 n3.set_state(SCons.Node.up_to_date)
608 # If the Node has had an exception recorded while it was getting
609 # prepared, then prepare() should raise that exception.
610 class MyException(Exception):
615 tm = SCons.Taskmaster.Taskmaster([n5])
616 tm.exc_type = MyException
617 tm.exc_value = "exception value"
622 except MyException, e:
626 assert exc_caught, "did not catch expected MyException"
627 assert str(e) == "exception value", e
628 assert built_text is None, built_text
630 # Regression test, make sure we prepare not only
631 # all targets, but their side effects as well.
638 n6.side_effects = [ n8 ]
639 n7.side_effects = [ n9, n10 ]
641 tm = SCons.Taskmaster.Taskmaster([n6, n7])
643 # More bogus reaching in and setting the targets.
652 def test_execute(self):
653 """Test executing a task
659 tm = SCons.Taskmaster.Taskmaster([n1])
662 assert built_text == "n1 built", built_text
664 def raise_UserError():
665 raise SCons.Errors.UserError
667 n2.build = raise_UserError
668 tm = SCons.Taskmaster.Taskmaster([n2])
672 except SCons.Errors.UserError:
675 raise TestFailed, "did not catch expected UserError"
677 def raise_BuildError():
678 raise SCons.Errors.BuildError
680 n3.build = raise_BuildError
681 tm = SCons.Taskmaster.Taskmaster([n3])
685 except SCons.Errors.BuildError:
688 raise TestFailed, "did not catch expected BuildError"
690 # On a generic (non-BuildError) exception from a Builder,
691 # the target should throw a BuildError exception with the
692 # args set to the exception value, instance, and traceback.
693 def raise_OtherError():
696 n4.build = raise_OtherError
697 tm = SCons.Taskmaster.Taskmaster([n4])
701 except SCons.Errors.BuildError, e:
702 assert e.node == n4, e.node
703 assert e.errstr == "Exception", e.errstr
704 assert len(e.args) == 3, `e.args`
705 assert e.args[0] == OtherError, e.args[0]
706 assert isinstance(e.args[1], OtherError), type(e.args[1])
707 assert type(e.args[2]) == type(sys.exc_traceback), e.args[2]
709 raise TestFailed, "did not catch expected BuildError"
711 def test_exception(self):
712 """Test generic Taskmaster exception handling
716 tm = SCons.Taskmaster.Taskmaster([n1])
718 tm.exception_set(1, 2)
719 assert tm.exc_type == 1, tm.exc_type
720 assert tm.exc_value == 2, tm.exc_value
723 assert tm.exc_type == 3, tm.exc_type
724 assert tm.exc_value is None, tm.exc_value
726 tm.exception_set(None, None)
727 assert tm.exc_type is None, tm.exc_type
728 assert tm.exc_value is None, tm.exc_value
730 tm.exception_set("exception 1", None)
734 assert sys.exc_type == "exception 1", sys.exc_type
735 assert sys.exc_value is None, sys.exc_value
737 assert 0, "did not catch expected exception"
739 tm.exception_set("exception 2", "xyzzy")
743 assert sys.exc_type == "exception 2", sys.exc_type
744 assert sys.exc_value == "xyzzy", sys.exc_value
746 assert 0, "did not catch expected exception"
750 if __name__ == "__main__":
751 suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
752 if not unittest.TextTestRunner().run(suite).wasSuccessful():