f74cf347392194b68fdbaacd817ad7eef832c44e
[scons.git] / src / engine / SCons / TaskmasterTests.py
1 #
2 # __COPYRIGHT__
3 #
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:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
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.
22 #
23
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
25
26 import copy
27 import sys
28 import unittest
29
30 import SCons.Taskmaster
31 import SCons.Errors
32
33
34 built_text = None
35 cache_text = []
36 visited_nodes = []
37 executed = None
38 scan_called = 0
39
40 class Node:
41     def __init__(self, name, kids = [], scans = []):
42         self.name = name
43         self.kids = kids
44         self.scans = scans
45         self.cached = 0
46         self.scanned = 0
47         self.scanner = None
48         self.targets = [self]
49         class Builder:
50             def targets(self, node):
51                 return node.targets
52         self.builder = Builder()
53         self.bsig = None
54         self.csig = None
55         self.state = SCons.Node.no_state
56         self.prepared = None
57         self.waiting_parents = {}
58         self.waiting_s_e = {}
59         self.side_effect = 0
60         self.side_effects = []
61         self.alttargets = []
62         self.postprocessed = None
63         self._bsig_val = None
64         self._current_val = 0
65
66     def disambiguate(self):
67         return self
68
69     def retrieve_from_cache(self):
70         global cache_text
71         if self.cached:
72             cache_text.append(self.name + " retrieved")
73         return self.cached
74
75     def build(self):
76         global built_text
77         built_text = self.name + " built"
78
79     def has_builder(self):
80         return not self.builder is None
81
82     def is_derived(self):
83         return self.has_builder or self.side_effect
84
85     def alter_targets(self):
86         return self.alttargets, None
87
88     def built(self):
89         global built_text
90         built_text = built_text + " really"
91
92     def visited(self):
93         global visited_nodes
94         visited_nodes.append(self.name)
95
96     def prepare(self):
97         self.prepared = 1
98
99     def children(self):
100         if not self.scanned:
101             self.scan()
102             self.scanned = 1
103         return self.kids
104
105     def scan(self):
106         global scan_called
107         scan_called = scan_called + 1
108         self.kids = self.kids + self.scans
109         self.scans = []
110
111     def scanner_key(self):
112         return self.name
113
114     def add_to_waiting_parents(self, node):
115         self.waiting_parents[node] = 1
116
117     def call_for_all_waiting_parents(self, func):
118         func(self)
119         for parent in self.waiting_parents.keys():
120             parent.call_for_all_waiting_parents(func)
121
122     def get_state(self):
123         return self.state
124
125     def set_state(self, state):
126         self.state = state
127
128     def set_bsig(self, bsig):
129         self.bsig = bsig
130
131     def set_csig(self, csig):
132         self.csig = csig
133
134     def store_csig(self):
135         pass
136
137     def store_bsig(self):
138         pass
139
140     def calculator(self):
141         class Calc:
142             def bsig(self, node):
143                 return node._bsig_val
144             def current(self, node, sig):
145                 return node._current_val
146         return Calc()
147
148     def current(self, calc=None):
149         if calc is None:
150             calc = self.calculator()
151         return calc.current(self, calc.bsig(self))
152     
153     def depends_on(self, nodes):
154         for node in nodes:
155             if node in self.kids:
156                 return 1
157         return 0
158
159     def __str__(self):
160         return self.name
161
162     def postprocess(self):
163         self.postprocessed = 1
164
165     def get_executor(self):
166         class Executor:
167             pass
168         e = Executor()
169         e.targets = self.targets
170         return e
171
172 class OtherError(Exception):
173     pass
174
175 class MyException(Exception):
176     pass
177
178
179 class TaskmasterTestCase(unittest.TestCase):
180
181     def test_next_task(self):
182         """Test fetching the next task
183         """
184         global built_text
185
186         n1 = Node("n1")
187         tm = SCons.Taskmaster.Taskmaster([n1, n1])
188         t = tm.next_task()
189         t.prepare()
190         t.execute()
191         t = tm.next_task()
192         assert t == None
193
194         n1 = Node("n1")
195         n2 = Node("n2")
196         n3 = Node("n3", [n1, n2])
197
198         tm = SCons.Taskmaster.Taskmaster([n3])
199
200         t = tm.next_task()
201         t.prepare()
202         t.execute()
203         assert built_text == "n1 built", built_text
204         t.executed()
205         t.postprocess()
206
207         t = tm.next_task()
208         t.prepare()
209         t.execute()
210         assert built_text == "n2 built", built_text
211         t.executed()
212         t.postprocess()
213
214         t = tm.next_task()
215         t.prepare()
216         t.execute()
217         assert built_text == "n3 built", built_text
218         t.executed()
219         t.postprocess()
220
221         assert tm.next_task() == None
222
223         built_text = "up to date: "
224         top_node = n3
225
226         class MyTask(SCons.Taskmaster.Task):
227             def execute(self):
228                 global built_text
229                 if self.targets[0].get_state() == SCons.Node.up_to_date:
230                     if self.top:
231                         built_text = self.targets[0].name + " up-to-date top"
232                     else:
233                         built_text = self.targets[0].name + " up-to-date"
234                 else:
235                     self.targets[0].build()
236
237         n1.set_state(SCons.Node.no_state)
238         n1._current_val = 1
239         n2.set_state(SCons.Node.no_state)
240         n2._current_val = 1
241         n3.set_state(SCons.Node.no_state)
242         n3._current_val = 1
243         tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
244
245         t = tm.next_task()
246         t.prepare()
247         t.execute()
248         assert built_text == "n1 up-to-date", built_text
249         t.executed()
250         t.postprocess()
251
252         t = tm.next_task()
253         t.prepare()
254         t.execute()
255         assert built_text == "n2 up-to-date", built_text
256         t.executed()
257         t.postprocess()
258
259         t = tm.next_task()
260         t.prepare()
261         t.execute()
262         assert built_text == "n3 up-to-date top", built_text
263         t.executed()
264         t.postprocess()
265
266         assert tm.next_task() == None
267
268
269         n1 = Node("n1")
270         n2 = Node("n2")
271         n3 = Node("n3", [n1, n2])
272         n4 = Node("n4")
273         n5 = Node("n5", [n3, n4])
274         tm = SCons.Taskmaster.Taskmaster([n5])
275
276         t1 = tm.next_task()
277         assert t1.get_target() == n1
278
279         t2 = tm.next_task()
280         assert t2.get_target() == n2
281
282         t4 = tm.next_task()
283         assert t4.get_target() == n4
284         t4.executed()
285         t4.postprocess()
286
287         t1.executed()
288         t1.postprocess()
289         t2.executed()
290         t2.postprocess()
291         t3 = tm.next_task()
292         assert t3.get_target() == n3
293
294         t3.executed()
295         t3.postprocess()
296         t5 = tm.next_task()
297         assert t5.get_target() == n5, t5.get_target()
298         t5.executed()
299         t5.postprocess()
300
301         assert tm.next_task() == None
302
303
304         n4 = Node("n4")
305         n4.set_state(SCons.Node.executed)
306         tm = SCons.Taskmaster.Taskmaster([n4])
307         assert tm.next_task() == None
308
309         n1 = Node("n1")
310         n2 = Node("n2", [n1])
311         tm = SCons.Taskmaster.Taskmaster([n2,n2])
312         t = tm.next_task()
313         t.executed()
314         t.postprocess()
315         t = tm.next_task()
316         assert tm.next_task() == None
317
318
319         n1 = Node("n1")
320         n2 = Node("n2")
321         n3 = Node("n3", [n1], [n2])
322         tm = SCons.Taskmaster.Taskmaster([n3])
323         t = tm.next_task()
324         target = t.get_target()
325         assert target == n1, target
326         t.executed()
327         t.postprocess()
328         t = tm.next_task()
329         target = t.get_target()
330         assert target == n2, target
331         t.executed()
332         t.postprocess()
333         t = tm.next_task()
334         target = t.get_target()
335         assert target == n3, target
336         t.executed()
337         t.postprocess()
338         assert tm.next_task() == None
339
340         n1 = Node("n1")
341         n2 = Node("n2")
342         n3 = Node("n3", [n1, n2])
343         n4 = Node("n4", [n3])
344         n5 = Node("n5", [n3])
345         global scan_called
346         scan_called = 0
347         tm = SCons.Taskmaster.Taskmaster([n4])
348         t = tm.next_task()
349         assert t.get_target() == n1
350         t.executed()
351         t.postprocess()
352         t = tm.next_task()
353         assert t.get_target() == n2
354         t.executed()
355         t.postprocess()
356         t = tm.next_task()
357         assert t.get_target() == n3
358         t.executed()
359         t.postprocess()
360         t = tm.next_task()
361         assert t.get_target() == n4
362         t.executed()
363         t.postprocess()
364         assert tm.next_task() == None
365         assert scan_called == 4, scan_called
366
367         tm = SCons.Taskmaster.Taskmaster([n5])
368         t = tm.next_task()
369         assert t.get_target() == n5, t.get_target()
370         t.executed()
371         assert tm.next_task() == None
372         assert scan_called == 5, scan_called
373
374         n1 = Node("n1")
375         n2 = Node("n2")
376         n3 = Node("n3")
377         n4 = Node("n4", [n1,n2,n3])
378         n5 = Node("n5", [n4])
379         n3.side_effect = 1
380         n1.side_effects = n2.side_effects = n3.side_effects = [n4]
381         tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
382         t = tm.next_task()
383         assert t.get_target() == n1
384         assert n4.state == SCons.Node.executing, n4.state
385         t.executed()
386         t.postprocess()
387         t = tm.next_task()
388         assert t.get_target() == n2
389         t.executed()
390         t.postprocess()
391         t = tm.next_task()
392         assert t.get_target() == n3
393         t.executed()
394         t.postprocess()
395         t = tm.next_task()
396         assert t.get_target() == n4
397         t.executed()
398         t.postprocess()
399         t = tm.next_task()
400         assert t.get_target() == n5
401         assert not tm.next_task()
402         t.executed()
403         t.postprocess()
404
405         n1 = Node("n1")
406         n2 = Node("n2")
407         n3 = Node("n3")
408         n4 = Node("n4", [n1,n2,n3])
409         def reverse(dependencies):
410             dependencies.reverse()
411             return dependencies
412         tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
413         t = tm.next_task()
414         assert t.get_target() == n3, t.get_target()
415         t.executed()
416         t.postprocess()
417         t = tm.next_task()
418         assert t.get_target() == n2, t.get_target()
419         t.executed()
420         t.postprocess()
421         t = tm.next_task()
422         assert t.get_target() == n1, t.get_target()
423         t.executed()
424         t.postprocess()
425         t = tm.next_task()
426         assert t.get_target() == n4, t.get_target()
427         t.executed()
428         t.postprocess()
429
430         n5 = Node("n5")
431         n6 = Node("n6")
432         n7 = Node("n7")
433         n6.alttargets = [n7]
434
435         tm = SCons.Taskmaster.Taskmaster([n5])
436         t = tm.next_task()
437         assert t.get_target() == n5
438         t.executed()
439         t.postprocess()
440
441         tm = SCons.Taskmaster.Taskmaster([n6])
442         t = tm.next_task()
443         assert t.get_target() == n7
444         t.executed()
445         t.postprocess()
446         t = tm.next_task()
447         assert t.get_target() == n6
448         t.executed()
449         t.postprocess()
450
451         n1 = Node("n1")
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
456
457         n1 = Node("n1")
458         n2 = Node("n2")
459         n1.targets = [n1, n2]
460         n1._current_val = 1
461         tm = SCons.Taskmaster.Taskmaster([n1])
462         t = tm.next_task()
463         t.executed()
464         t.postprocess()
465
466         s = n1.get_state()
467         assert s == SCons.Node.up_to_date, s
468         s = n2.get_state()
469         assert s == SCons.Node.executed, s
470
471
472     def test_make_ready_out_of_date(self):
473         """Test the Task.make_ready() method's list of out-of-date Nodes
474         """
475         ood = []
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)
482             t.ood = ood
483             return t
484
485         n1 = Node("n1")
486         c2 = Node("c2")
487         c2._current_val = 1
488         n3 = Node("n3")
489         c4 = Node("c4")
490         c4._current_val = 1
491         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
492                                          tasker = TaskGen)
493
494         del ood[:]
495         t = tm.next_task()
496         assert ood == [n1], ood
497
498         del ood[:]
499         t = tm.next_task()
500         assert ood == [], ood
501
502         del ood[:]
503         t = tm.next_task()
504         assert ood == [n3], ood
505
506         del ood[:]
507         t = tm.next_task()
508         assert ood == [], ood
509
510
511     def test_make_ready_exception(self):
512         """Test handling exceptions from Task.make_ready()
513         """
514         class MyTask(SCons.Taskmaster.Task):
515             def make_ready(self):
516                 raise MyException, "from make_ready()"
517
518         n1 = Node("n1")
519         tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
520         t = tm.next_task()
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
524
525
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
530
531         n1 = Node("n1")
532         c2 = Node("c2")
533         c2._current_val = 1
534         n3 = Node("n3")
535         c4 = Node("c4")
536         c4._current_val = 1
537
538         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
539
540         t = tm.next_task()
541         target = t.get_target()
542         assert target is n1, target
543         assert target.state == SCons.Node.executing, target.state
544         t = tm.next_task()
545         target = t.get_target()
546         assert target is c2, target
547         assert target.state == SCons.Node.up_to_date, target.state
548         t = tm.next_task()
549         target = t.get_target()
550         assert target is n3, target
551         assert target.state == SCons.Node.executing, target.state
552         t = tm.next_task()
553         target = t.get_target()
554         assert target is c4, target
555         assert target.state == SCons.Node.up_to_date, target.state
556         t = tm.next_task()
557         assert t is None
558
559         n1 = Node("n1")
560         c2 = Node("c2")
561         n3 = Node("n3")
562         c4 = Node("c4")
563
564         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
565                                          tasker = MyTask)
566
567         t = tm.next_task()
568         target = t.get_target()
569         assert target is n1, target
570         assert target.state == SCons.Node.executing, target.state
571         t = tm.next_task()
572         target = t.get_target()
573         assert target is c2, target
574         assert target.state == SCons.Node.executing, target.state
575         t = tm.next_task()
576         target = t.get_target()
577         assert target is n3, target
578         assert target.state == SCons.Node.executing, target.state
579         t = tm.next_task()
580         target = t.get_target()
581         assert target is c4, target
582         assert target.state == SCons.Node.executing, target.state
583         t = tm.next_task()
584         assert t is None
585
586
587     def test_children_errors(self):
588         """Test errors when fetching the children of a node.
589         """
590         class StopNode(Node):
591             def children(self):
592                 raise SCons.Errors.StopError, "stop!"
593         class ExitNode(Node):
594             def children(self):
595                 sys.exit(77)
596
597         n1 = StopNode("n1")
598         tm = SCons.Taskmaster.Taskmaster([n1])
599         t = tm.next_task()
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
603
604         n2 = ExitNode("n2")
605         tm = SCons.Taskmaster.Taskmaster([n2])
606         t = tm.next_task()
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
611
612     def test_cycle_detection(self):
613         """Test detecting dependency cycles
614         """
615         n1 = Node("n1")
616         n2 = Node("n2", [n1])
617         n3 = Node("n3", [n2])
618         n1.kids = [n3]
619
620         tm = SCons.Taskmaster.Taskmaster([n3])
621         try:
622             t = tm.next_task()
623         except SCons.Errors.UserError, e:
624             assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
625         else:
626             assert 'Did not catch expected UserError'
627
628     def test_next_top_level_candidate(self):
629         """Test the next_top_level_candidate() method
630         """
631         n1 = Node("n1")
632         n2 = Node("n2", [n1])
633         n3 = Node("n3", [n2])
634
635         tm = SCons.Taskmaster.Taskmaster([n3])
636         t = tm.next_task()
637         assert t.targets == [n1], t.targets
638         t.fail_stop()
639         assert t.targets == [n3], map(str, t.targets)
640         assert t.top == 1, t.top
641
642     def test_stop(self):
643         """Test the stop() method
644
645         Both default and overridden in a subclass.
646         """
647         global built_text
648
649         n1 = Node("n1")
650         n2 = Node("n2")
651         n3 = Node("n3", [n1, n2])
652
653         tm = SCons.Taskmaster.Taskmaster([n3])
654         t = tm.next_task()
655         t.prepare()
656         t.execute()
657         assert built_text == "n1 built", built_text
658         t.executed()
659         t.postprocess()
660         assert built_text == "n1 built really", built_text
661
662         tm.stop()
663         assert tm.next_task() is None
664
665         class MyTM(SCons.Taskmaster.Taskmaster):
666             def stop(self):
667                 global built_text
668                 built_text = "MyTM.stop()"
669                 SCons.Taskmaster.Taskmaster.stop(self)
670
671         n1 = Node("n1")
672         n2 = Node("n2")
673         n3 = Node("n3", [n1, n2])
674
675         built_text = None
676         tm = MyTM([n3])
677         tm.next_task().execute()
678         assert built_text == "n1 built"
679
680         tm.stop()
681         assert built_text == "MyTM.stop()"
682         assert tm.next_task() is None
683
684     def test_executed(self):
685         """Test when a task has been executed
686         """
687         global built_text
688         global visited_nodes
689
690         n1 = Node("n1")
691         tm = SCons.Taskmaster.Taskmaster([n1])
692         t = tm.next_task()
693         built_text = "xxx"
694         visited_nodes = []
695         n1.set_state(SCons.Node.executing)
696
697         t.executed()
698
699         s = n1.get_state()
700         assert s == SCons.Node.executed, s
701         assert built_text == "xxx really", built_text
702         assert visited_nodes == [], visited_nodes
703
704         n2 = Node("n2")
705         tm = SCons.Taskmaster.Taskmaster([n2])
706         t = tm.next_task()
707         built_text = "should_not_change"
708         visited_nodes = []
709         n2.set_state(None)
710
711         t.executed()
712
713         s = n2.get_state()
714         assert s is None, s
715         assert built_text == "should_not_change", built_text
716         assert visited_nodes == ['n2'], visited_nodes
717
718         n3 = Node("n3")
719         n4 = Node("n4")
720         n3.targets = [n3, n4]
721         tm = SCons.Taskmaster.Taskmaster([n3])
722         t = tm.next_task()
723         visited_nodes = []
724         n3.set_state(SCons.Node.up_to_date)
725         n4.set_state(SCons.Node.executing)
726
727         t.executed()
728
729         s = n3.get_state()
730         assert s == SCons.Node.up_to_date, s
731         s = n4.get_state()
732         assert s == SCons.Node.executed, s
733         assert visited_nodes == ['n3'], visited_nodes
734
735     def test_prepare(self):
736         """Test preparation of multiple Nodes for a task
737         """
738         n1 = Node("n1")
739         n2 = Node("n2")
740         tm = SCons.Taskmaster.Taskmaster([n1, n2])
741         t = tm.next_task()
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.
748         t.targets = [n1, n2]
749         t.prepare()
750         assert n1.prepared
751         assert n2.prepared
752
753         n3 = Node("n3")
754         n4 = Node("n4")
755         tm = SCons.Taskmaster.Taskmaster([n3, n4])
756         t = tm.next_task()
757         # More bogus reaching in and setting the targets.
758         n3.set_state(SCons.Node.up_to_date)
759         t.targets = [n3, n4]
760         t.prepare()
761         assert n3.prepared
762         assert n4.prepared
763
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):
767             pass
768
769         built_text = None
770         n5 = Node("n5")
771         tm = SCons.Taskmaster.Taskmaster([n5])
772         t = tm.next_task()
773         t.exception_set((MyException, "exception value"))
774         exc_caught = None
775         try:
776             t.prepare()
777         except MyException, e:
778             exc_caught = 1
779         except:
780             pass
781         assert exc_caught, "did not catch expected MyException"
782         assert str(e) == "exception value", e
783         assert built_text is None, built_text
784
785         # Regression test, make sure we prepare not only
786         # all targets, but their side effects as well.
787         n6 = Node("n6")
788         n7 = Node("n7")
789         n8 = Node("n8")
790         n9 = Node("n9")
791         n10 = Node("n10")
792
793         n6.side_effects = [ n8 ]
794         n7.side_effects = [ n9, n10 ]
795
796         tm = SCons.Taskmaster.Taskmaster([n6, n7])
797         t = tm.next_task()
798         # More bogus reaching in and setting the targets.
799         t.targets = [n6, n7]
800         t.prepare()
801         assert n6.prepared
802         assert n7.prepared
803         assert n8.prepared
804         assert n9.prepared
805         assert n10.prepared
806
807     def test_execute(self):
808         """Test executing a task
809         """
810         global built_text
811         global cache_text
812
813         n1 = Node("n1")
814         tm = SCons.Taskmaster.Taskmaster([n1])
815         t = tm.next_task()
816         t.execute()
817         assert built_text == "n1 built", built_text
818
819         def raise_UserError():
820             raise SCons.Errors.UserError
821         n2 = Node("n2")
822         n2.build = raise_UserError
823         tm = SCons.Taskmaster.Taskmaster([n2])
824         t = tm.next_task()
825         try:
826             t.execute()
827         except SCons.Errors.UserError:
828             pass
829         else:
830             raise TestFailed, "did not catch expected UserError"
831
832         def raise_BuildError():
833             raise SCons.Errors.BuildError
834         n3 = Node("n3")
835         n3.build = raise_BuildError
836         tm = SCons.Taskmaster.Taskmaster([n3])
837         t = tm.next_task()
838         try:
839             t.execute()
840         except SCons.Errors.BuildError:
841             pass
842         else:
843             raise TestFailed, "did not catch expected BuildError"
844
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():
849             raise OtherError
850         n4 = Node("n4")
851         n4.build = raise_OtherError
852         tm = SCons.Taskmaster.Taskmaster([n4])
853         t = tm.next_task()
854         try:
855             t.execute()
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]
862         else:
863             raise TestFailed, "did not catch expected BuildError"
864
865         built_text = None
866         cache_text = []
867         n5 = Node("n5")
868         n6 = Node("n6")
869         n6.cached = 1
870         tm = SCons.Taskmaster.Taskmaster([n5])
871         t = tm.next_task()
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.
878         t.targets = [n5, n6]
879         t.execute()
880         assert built_text == "n5 built", built_text
881         assert cache_text == [], cache_text
882
883         built_text = None
884         cache_text = []
885         n7 = Node("n7")
886         n8 = Node("n8")
887         n7.cached = 1
888         n8.cached = 1
889         tm = SCons.Taskmaster.Taskmaster([n7])
890         t = tm.next_task()
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.
897         t.targets = [n7, n8]
898         t.execute()
899         assert built_text is None, built_text
900         assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
901
902     def test_exception(self):
903         """Test generic Taskmaster exception handling
904
905         """
906         n1 = Node("n1")
907         tm = SCons.Taskmaster.Taskmaster([n1])
908         t  = tm.next_task()
909
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
914
915         t.exception_set(3)
916         assert t.exception == 3
917
918         try: 1/0
919         except: pass
920         t.exception_set(None)
921         exc_type, exc_value, exc_tb = t.exception
922         assert exc_type is ZeroDivisionError, exc_type
923         exception_values = [
924             "integer division or modulo",
925             "integer division or modulo by zero",
926         ]
927         assert str(exc_value) in exception_values, exc_value
928
929         t.exception_set(("exception 1", None))
930         try:
931             t.exception_raise()
932         except:
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
936         else:
937             assert 0, "did not catch expected exception"
938
939         t.exception_set(("exception 2", "xyzzy"))
940         try:
941             t.exception_raise()
942         except:
943             exc_type, exc_value = sys.exc_info()[:2]
944             assert exc_type == "exception 2", exc_type
945             assert exc_value == "xyzzy", exc_value
946         else:
947             assert 0, "did not catch expected exception"
948
949         try:
950             1/0
951         except:
952             tb = sys.exc_info()[2]
953         t.exception_set(("exception 3", "arg", tb))
954         try:
955             t.exception_raise()
956         except:
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
960             import traceback
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)
964         else:
965             assert 0, "did not catch expected exception"
966
967     def test_postprocess(self):
968         """Test postprocessing targets to give them a chance to clean up
969         """
970         n1 = Node("n1")
971         tm = SCons.Taskmaster.Taskmaster([n1])
972
973         t = tm.next_task()
974         assert not n1.postprocessed
975         t.postprocess()
976         assert n1.postprocessed
977
978         n2 = Node("n2")
979         n3 = Node("n3")
980         tm = SCons.Taskmaster.Taskmaster([n2, n3])
981
982         assert not n2.postprocessed
983         assert not n3.postprocessed
984         t = tm.next_task()
985         t.postprocess()
986         assert n2.postprocessed
987         assert not n3.postprocessed
988         t = tm.next_task()
989         t.postprocess()
990         assert n2.postprocessed
991         assert n3.postprocessed
992
993     def test_trace(self):
994         """Test Taskmaster tracing
995         """
996         import StringIO
997
998         trace = StringIO.StringIO()
999         n1 = Node("n1")
1000         n2 = Node("n2")
1001         n3 = Node("n3", [n1, n2])
1002         tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1003         t = tm.next_task()
1004         t.prepare()
1005         t.execute()
1006         n1.set_state(SCons.Node.executed)
1007         t = tm.next_task()
1008         t.prepare()
1009         t.execute()
1010         t = tm.next_task()
1011
1012         value = trace.getvalue()
1013         expect = """\
1014 Taskmaster: 'n1': evaluating n1
1015 Taskmaster: 'n1': already handled (executed)
1016 Taskmaster: 'n3': children:
1017     ['n1', 'n2']
1018     waiting on unstarted children:
1019     ['n2']
1020 Taskmaster: 'n2': evaluating n2
1021 Taskmaster: 'n3': children:
1022     ['n1', 'n2']
1023     waiting on unfinished children:
1024     ['n2']
1025 """
1026         assert value == expect, value
1027
1028
1029
1030 if __name__ == "__main__":
1031     suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1032     if not unittest.TextTestRunner().run(suite).wasSuccessful():
1033         sys.exit(1)