917bb622479636224ade248472d425d4783f80c8
[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 SCons.compat
27
28 import copy
29 import sys
30 import unittest
31
32 import SCons.Taskmaster
33 import SCons.Errors
34
35
36 built_text = None
37 cache_text = []
38 visited_nodes = []
39 executed = None
40 scan_called = 0
41
42 class Node:
43     def __init__(self, name, kids = [], scans = []):
44         self.name = name
45         self.kids = kids
46         self.scans = scans
47         self.cached = 0
48         self.scanned = 0
49         self.scanner = None
50         self.targets = [self]
51         self.prerequisites = []
52         class Builder:
53             def targets(self, node):
54                 return node.targets
55         self.builder = Builder()
56         self.bsig = None
57         self.csig = None
58         self.state = SCons.Node.no_state
59         self.prepared = None
60         self.ref_count = 0
61         self.waiting_parents = set()
62         self.waiting_s_e = set()
63         self.side_effect = 0
64         self.side_effects = []
65         self.alttargets = []
66         self.postprocessed = None
67         self._bsig_val = None
68         self._current_val = 0
69         self.always_build = None
70
71     def disambiguate(self):
72         return self
73
74     def push_to_cache(self):
75         pass
76
77     def retrieve_from_cache(self):
78         global cache_text
79         if self.cached:
80             cache_text.append(self.name + " retrieved")
81         return self.cached
82
83     def make_ready(self):
84         pass
85
86     def prepare(self):
87         self.prepared = 1
88
89     def build(self):
90         global built_text
91         built_text = self.name + " built"
92
93     def built(self):
94         global built_text
95         if not self.cached:
96             built_text = built_text + " really"
97
98     def has_builder(self):
99         return not self.builder is None
100
101     def is_derived(self):
102         return self.has_builder or self.side_effect
103
104     def alter_targets(self):
105         return self.alttargets, None
106
107     def visited(self):
108         global visited_nodes
109         visited_nodes.append(self.name)
110
111     def children(self):
112         if not self.scanned:
113             self.scan()
114             self.scanned = 1
115         return self.kids
116
117     def scan(self):
118         global scan_called
119         scan_called = scan_called + 1
120         self.kids = self.kids + self.scans
121         self.scans = []
122
123     def scanner_key(self):
124         return self.name
125
126     def add_to_waiting_parents(self, node):
127         wp = self.waiting_parents
128         if node in wp:
129             return 0
130         wp.add(node)
131         return 1
132
133     def get_state(self):
134         return self.state
135
136     def set_state(self, state):
137         self.state = state
138
139     def set_bsig(self, bsig):
140         self.bsig = bsig
141
142     def set_csig(self, csig):
143         self.csig = csig
144
145     def store_csig(self):
146         pass
147
148     def store_bsig(self):
149         pass
150
151     def is_pseudo_derived(self):
152         pass
153
154     def is_up_to_date(self):
155         return self._current_val
156     
157     def depends_on(self, nodes):
158         for node in nodes:
159             if node in self.kids:
160                 return 1
161         return 0
162
163     def __str__(self):
164         return self.name
165
166     def postprocess(self):
167         self.postprocessed = 1
168         self.waiting_parents = set()
169
170     def get_executor(self):
171         if not hasattr(self, 'executor'):
172             class Executor:
173                 def prepare(self):
174                     pass
175                 def get_action_targets(self):
176                     return self.targets
177                 def get_all_targets(self):
178                     return self.targets
179                 def get_all_children(self):
180                     result = []
181                     for node in self.targets:
182                         result.extend(node.children())
183                     return result
184                 def get_all_prerequisites(self):
185                     return []
186                 def get_action_side_effects(self):
187                     return []
188             self.executor = Executor()
189             self.executor.targets = self.targets
190         return self.executor
191
192 class OtherError(Exception):
193     pass
194
195 class MyException(Exception):
196     pass
197
198
199 class TaskmasterTestCase(unittest.TestCase):
200
201     def test_next_task(self):
202         """Test fetching the next task
203         """
204         global built_text
205
206         n1 = Node("n1")
207         tm = SCons.Taskmaster.Taskmaster([n1, n1])
208         t = tm.next_task()
209         t.prepare()
210         t.execute()
211         t = tm.next_task()
212         assert t is None
213
214         n1 = Node("n1")
215         n2 = Node("n2")
216         n3 = Node("n3", [n1, n2])
217
218         tm = SCons.Taskmaster.Taskmaster([n3])
219
220         t = tm.next_task()
221         t.prepare()
222         t.execute()
223         assert built_text == "n1 built", built_text
224         t.executed()
225         t.postprocess()
226
227         t = tm.next_task()
228         t.prepare()
229         t.execute()
230         assert built_text == "n2 built", built_text
231         t.executed()
232         t.postprocess()
233
234         t = tm.next_task()
235         t.prepare()
236         t.execute()
237         assert built_text == "n3 built", built_text
238         t.executed()
239         t.postprocess()
240
241         assert tm.next_task() is None
242
243         built_text = "up to date: "
244         top_node = n3
245
246         class MyTask(SCons.Taskmaster.Task):
247             def execute(self):
248                 global built_text
249                 if self.targets[0].get_state() == SCons.Node.up_to_date:
250                     if self.top:
251                         built_text = self.targets[0].name + " up-to-date top"
252                     else:
253                         built_text = self.targets[0].name + " up-to-date"
254                 else:
255                     self.targets[0].build()
256
257         n1.set_state(SCons.Node.no_state)
258         n1._current_val = 1
259         n2.set_state(SCons.Node.no_state)
260         n2._current_val = 1
261         n3.set_state(SCons.Node.no_state)
262         n3._current_val = 1
263         tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
264
265         t = tm.next_task()
266         t.prepare()
267         t.execute()
268         assert built_text == "n1 up-to-date", built_text
269         t.executed()
270         t.postprocess()
271
272         t = tm.next_task()
273         t.prepare()
274         t.execute()
275         assert built_text == "n2 up-to-date", built_text
276         t.executed()
277         t.postprocess()
278
279         t = tm.next_task()
280         t.prepare()
281         t.execute()
282         assert built_text == "n3 up-to-date top", built_text
283         t.executed()
284         t.postprocess()
285
286         assert tm.next_task() is None
287
288
289         n1 = Node("n1")
290         n2 = Node("n2")
291         n3 = Node("n3", [n1, n2])
292         n4 = Node("n4")
293         n5 = Node("n5", [n3, n4])
294         tm = SCons.Taskmaster.Taskmaster([n5])
295
296         t1 = tm.next_task()
297         assert t1.get_target() == n1
298
299         t2 = tm.next_task()
300         assert t2.get_target() == n2
301
302         t4 = tm.next_task()
303         assert t4.get_target() == n4
304         t4.executed()
305         t4.postprocess()
306
307         t1.executed()
308         t1.postprocess()
309         t2.executed()
310         t2.postprocess()
311         t3 = tm.next_task()
312         assert t3.get_target() == n3
313
314         t3.executed()
315         t3.postprocess()
316         t5 = tm.next_task()
317         assert t5.get_target() == n5, t5.get_target()
318         t5.executed()
319         t5.postprocess()
320
321         assert tm.next_task() is None
322
323
324         n4 = Node("n4")
325         n4.set_state(SCons.Node.executed)
326         tm = SCons.Taskmaster.Taskmaster([n4])
327         assert tm.next_task() is None
328
329         n1 = Node("n1")
330         n2 = Node("n2", [n1])
331         tm = SCons.Taskmaster.Taskmaster([n2,n2])
332         t = tm.next_task()
333         t.executed()
334         t.postprocess()
335         t = tm.next_task()
336         assert tm.next_task() is None
337
338
339         n1 = Node("n1")
340         n2 = Node("n2")
341         n3 = Node("n3", [n1], [n2])
342         tm = SCons.Taskmaster.Taskmaster([n3])
343         t = tm.next_task()
344         target = t.get_target()
345         assert target == n1, target
346         t.executed()
347         t.postprocess()
348         t = tm.next_task()
349         target = t.get_target()
350         assert target == n2, target
351         t.executed()
352         t.postprocess()
353         t = tm.next_task()
354         target = t.get_target()
355         assert target == n3, target
356         t.executed()
357         t.postprocess()
358         assert tm.next_task() is None
359
360         n1 = Node("n1")
361         n2 = Node("n2")
362         n3 = Node("n3", [n1, n2])
363         n4 = Node("n4", [n3])
364         n5 = Node("n5", [n3])
365         global scan_called
366         scan_called = 0
367         tm = SCons.Taskmaster.Taskmaster([n4])
368         t = tm.next_task()
369         assert t.get_target() == n1
370         t.executed()
371         t.postprocess()
372         t = tm.next_task()
373         assert t.get_target() == n2
374         t.executed()
375         t.postprocess()
376         t = tm.next_task()
377         assert t.get_target() == n3
378         t.executed()
379         t.postprocess()
380         t = tm.next_task()
381         assert t.get_target() == n4
382         t.executed()
383         t.postprocess()
384         assert tm.next_task() is None
385         assert scan_called == 4, scan_called
386
387         tm = SCons.Taskmaster.Taskmaster([n5])
388         t = tm.next_task()
389         assert t.get_target() == n5, t.get_target()
390         t.executed()
391         assert tm.next_task() is None
392         assert scan_called == 5, scan_called
393
394         n1 = Node("n1")
395         n2 = Node("n2")
396         n3 = Node("n3")
397         n4 = Node("n4", [n1,n2,n3])
398         n5 = Node("n5", [n4])
399         n3.side_effect = 1
400         n1.side_effects = n2.side_effects = n3.side_effects = [n4]
401         tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
402         t = tm.next_task()
403         assert t.get_target() == n1
404         assert n4.state == SCons.Node.executing, n4.state
405         t.executed()
406         t.postprocess()
407         t = tm.next_task()
408         assert t.get_target() == n2
409         t.executed()
410         t.postprocess()
411         t = tm.next_task()
412         assert t.get_target() == n3
413         t.executed()
414         t.postprocess()
415         t = tm.next_task()
416         assert t.get_target() == n4
417         t.executed()
418         t.postprocess()
419         t = tm.next_task()
420         assert t.get_target() == n5
421         assert not tm.next_task()
422         t.executed()
423         t.postprocess()
424
425         n1 = Node("n1")
426         n2 = Node("n2")
427         n3 = Node("n3")
428         n4 = Node("n4", [n1,n2,n3])
429         def reverse(dependencies):
430             dependencies.reverse()
431             return dependencies
432         tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
433         t = tm.next_task()
434         assert t.get_target() == n3, t.get_target()
435         t.executed()
436         t.postprocess()
437         t = tm.next_task()
438         assert t.get_target() == n2, t.get_target()
439         t.executed()
440         t.postprocess()
441         t = tm.next_task()
442         assert t.get_target() == n1, t.get_target()
443         t.executed()
444         t.postprocess()
445         t = tm.next_task()
446         assert t.get_target() == n4, t.get_target()
447         t.executed()
448         t.postprocess()
449
450         n5 = Node("n5")
451         n6 = Node("n6")
452         n7 = Node("n7")
453         n6.alttargets = [n7]
454
455         tm = SCons.Taskmaster.Taskmaster([n5])
456         t = tm.next_task()
457         assert t.get_target() == n5
458         t.executed()
459         t.postprocess()
460
461         tm = SCons.Taskmaster.Taskmaster([n6])
462         t = tm.next_task()
463         assert t.get_target() == n7
464         t.executed()
465         t.postprocess()
466         t = tm.next_task()
467         assert t.get_target() == n6
468         t.executed()
469         t.postprocess()
470
471         n1 = Node("n1")
472         n2 = Node("n2", [n1])
473         n1.set_state(SCons.Node.failed)
474         tm = SCons.Taskmaster.Taskmaster([n2])
475         assert tm.next_task() is None
476
477         n1 = Node("n1")
478         n2 = Node("n2")
479         n1.targets = [n1, n2]
480         n1._current_val = 1
481         tm = SCons.Taskmaster.Taskmaster([n1])
482         t = tm.next_task()
483         t.executed()
484         t.postprocess()
485
486         s = n1.get_state()
487         assert s == SCons.Node.executed, s
488         s = n2.get_state()
489         assert s == SCons.Node.executed, s
490
491
492     def test_make_ready_out_of_date(self):
493         """Test the Task.make_ready() method's list of out-of-date Nodes
494         """
495         ood = []
496         def TaskGen(tm, targets, top, node, ood=ood):
497             class MyTask(SCons.Taskmaster.Task):
498                 def make_ready(self):
499                     SCons.Taskmaster.Task.make_ready(self)
500                     self.ood.extend(self.out_of_date)
501             t = MyTask(tm, targets, top, node)
502             t.ood = ood
503             return t
504
505         n1 = Node("n1")
506         c2 = Node("c2")
507         c2._current_val = 1
508         n3 = Node("n3")
509         c4 = Node("c4")
510         c4._current_val = 1
511         a5 = Node("a5")
512         a5._current_val = 1
513         a5.always_build = 1
514         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4, a5],
515                                          tasker = TaskGen)
516
517         del ood[:]
518         t = tm.next_task()
519         assert ood == [n1], ood
520
521         del ood[:]
522         t = tm.next_task()
523         assert ood == [], ood
524
525         del ood[:]
526         t = tm.next_task()
527         assert ood == [n3], ood
528
529         del ood[:]
530         t = tm.next_task()
531         assert ood == [], ood
532
533         del ood[:]
534         t = tm.next_task()
535         assert ood == [a5], ood
536
537     def test_make_ready_exception(self):
538         """Test handling exceptions from Task.make_ready()
539         """
540         class MyTask(SCons.Taskmaster.Task):
541             def make_ready(self):
542                 raise MyException, "from make_ready()"
543
544         n1 = Node("n1")
545         tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
546         t = tm.next_task()
547         exc_type, exc_value, exc_tb = t.exception
548         assert exc_type == MyException, repr(exc_type)
549         assert str(exc_value) == "from make_ready()", exc_value
550
551
552     def test_make_ready_all(self):
553         """Test the make_ready_all() method"""
554         class MyTask(SCons.Taskmaster.Task):
555             make_ready = SCons.Taskmaster.Task.make_ready_all
556
557         n1 = Node("n1")
558         c2 = Node("c2")
559         c2._current_val = 1
560         n3 = Node("n3")
561         c4 = Node("c4")
562         c4._current_val = 1
563
564         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
565
566         t = tm.next_task()
567         target = t.get_target()
568         assert target is n1, target
569         assert target.state == SCons.Node.executing, target.state
570         t = tm.next_task()
571         target = t.get_target()
572         assert target is c2, target
573         assert target.state == SCons.Node.up_to_date, target.state
574         t = tm.next_task()
575         target = t.get_target()
576         assert target is n3, target
577         assert target.state == SCons.Node.executing, target.state
578         t = tm.next_task()
579         target = t.get_target()
580         assert target is c4, target
581         assert target.state == SCons.Node.up_to_date, target.state
582         t = tm.next_task()
583         assert t is None
584
585         n1 = Node("n1")
586         c2 = Node("c2")
587         n3 = Node("n3")
588         c4 = Node("c4")
589
590         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
591                                          tasker = MyTask)
592
593         t = tm.next_task()
594         target = t.get_target()
595         assert target is n1, target
596         assert target.state == SCons.Node.executing, target.state
597         t = tm.next_task()
598         target = t.get_target()
599         assert target is c2, target
600         assert target.state == SCons.Node.executing, target.state
601         t = tm.next_task()
602         target = t.get_target()
603         assert target is n3, target
604         assert target.state == SCons.Node.executing, target.state
605         t = tm.next_task()
606         target = t.get_target()
607         assert target is c4, target
608         assert target.state == SCons.Node.executing, target.state
609         t = tm.next_task()
610         assert t is None
611
612
613     def test_children_errors(self):
614         """Test errors when fetching the children of a node.
615         """
616         class StopNode(Node):
617             def children(self):
618                 raise SCons.Errors.StopError, "stop!"
619         class ExitNode(Node):
620             def children(self):
621                 sys.exit(77)
622
623         n1 = StopNode("n1")
624         tm = SCons.Taskmaster.Taskmaster([n1])
625         t = tm.next_task()
626         exc_type, exc_value, exc_tb = t.exception
627         assert exc_type == SCons.Errors.StopError, repr(exc_type)
628         assert str(exc_value) == "stop!", exc_value
629
630         n2 = ExitNode("n2")
631         tm = SCons.Taskmaster.Taskmaster([n2])
632         t = tm.next_task()
633         exc_type, exc_value = t.exception
634         assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
635         assert exc_value.node == n2, exc_value.node
636         assert exc_value.status == 77, exc_value.status
637
638     def test_cycle_detection(self):
639         """Test detecting dependency cycles
640         """
641         n1 = Node("n1")
642         n2 = Node("n2", [n1])
643         n3 = Node("n3", [n2])
644         n1.kids = [n3]
645
646         tm = SCons.Taskmaster.Taskmaster([n3])
647         try:
648             t = tm.next_task()
649         except SCons.Errors.UserError, e:
650             assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
651         else:
652             assert 'Did not catch expected UserError'
653
654     def test_next_top_level_candidate(self):
655         """Test the next_top_level_candidate() method
656         """
657         n1 = Node("n1")
658         n2 = Node("n2", [n1])
659         n3 = Node("n3", [n2])
660
661         tm = SCons.Taskmaster.Taskmaster([n3])
662         t = tm.next_task()
663         assert t.targets == [n1], t.targets
664         t.fail_stop()
665         assert t.targets == [n3], list(map(str, t.targets))
666         assert t.top == 1, t.top
667
668     def test_stop(self):
669         """Test the stop() method
670
671         Both default and overridden in a subclass.
672         """
673         global built_text
674
675         n1 = Node("n1")
676         n2 = Node("n2")
677         n3 = Node("n3", [n1, n2])
678
679         tm = SCons.Taskmaster.Taskmaster([n3])
680         t = tm.next_task()
681         t.prepare()
682         t.execute()
683         assert built_text == "n1 built", built_text
684         t.executed()
685         t.postprocess()
686         assert built_text == "n1 built really", built_text
687
688         tm.stop()
689         assert tm.next_task() is None
690
691         class MyTM(SCons.Taskmaster.Taskmaster):
692             def stop(self):
693                 global built_text
694                 built_text = "MyTM.stop()"
695                 SCons.Taskmaster.Taskmaster.stop(self)
696
697         n1 = Node("n1")
698         n2 = Node("n2")
699         n3 = Node("n3", [n1, n2])
700
701         built_text = None
702         tm = MyTM([n3])
703         tm.next_task().execute()
704         assert built_text == "n1 built"
705
706         tm.stop()
707         assert built_text == "MyTM.stop()"
708         assert tm.next_task() is None
709
710     def test_executed(self):
711         """Test when a task has been executed
712         """
713         global built_text
714         global visited_nodes
715
716         n1 = Node("n1")
717         tm = SCons.Taskmaster.Taskmaster([n1])
718         t = tm.next_task()
719         built_text = "xxx"
720         visited_nodes = []
721         n1.set_state(SCons.Node.executing)
722
723         t.executed()
724
725         s = n1.get_state()
726         assert s == SCons.Node.executed, s
727         assert built_text == "xxx really", built_text
728         assert visited_nodes == ['n1'], visited_nodes
729
730         n2 = Node("n2")
731         tm = SCons.Taskmaster.Taskmaster([n2])
732         t = tm.next_task()
733         built_text = "should_not_change"
734         visited_nodes = []
735         n2.set_state(None)
736
737         t.executed()
738
739         s = n2.get_state()
740         assert s is None, s
741         assert built_text == "should_not_change", built_text
742         assert visited_nodes == ['n2'], visited_nodes
743
744         n3 = Node("n3")
745         n4 = Node("n4")
746         n3.targets = [n3, n4]
747         tm = SCons.Taskmaster.Taskmaster([n3])
748         t = tm.next_task()
749         visited_nodes = []
750         n3.set_state(SCons.Node.up_to_date)
751         n4.set_state(SCons.Node.executing)
752
753         t.executed()
754
755         s = n3.get_state()
756         assert s == SCons.Node.up_to_date, s
757         s = n4.get_state()
758         assert s == SCons.Node.executed, s
759         assert visited_nodes == ['n3', 'n4'], visited_nodes
760
761     def test_prepare(self):
762         """Test preparation of multiple Nodes for a task
763         """
764         n1 = Node("n1")
765         n2 = Node("n2")
766         tm = SCons.Taskmaster.Taskmaster([n1, n2])
767         t = tm.next_task()
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.
774         n1.get_executor().targets = [n1, n2]
775         t.prepare()
776         assert n1.prepared
777         assert n2.prepared
778
779         n3 = Node("n3")
780         n4 = Node("n4")
781         tm = SCons.Taskmaster.Taskmaster([n3, n4])
782         t = tm.next_task()
783         # More bogus reaching in and setting the targets.
784         n3.set_state(SCons.Node.up_to_date)
785         n3.get_executor().targets = [n3, n4]
786         t.prepare()
787         assert n3.prepared
788         assert n4.prepared
789
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):
793             pass
794
795         built_text = None
796         n5 = Node("n5")
797         tm = SCons.Taskmaster.Taskmaster([n5])
798         t = tm.next_task()
799         t.exception_set((MyException, "exception value"))
800         exc_caught = None
801         try:
802             t.prepare()
803         except MyException, e:
804             exc_caught = 1
805         except:
806             pass
807         assert exc_caught, "did not catch expected MyException"
808         assert str(e) == "exception value", e
809         assert built_text is None, built_text
810
811         # Regression test, make sure we prepare not only
812         # all targets, but their side effects as well.
813         n6 = Node("n6")
814         n7 = Node("n7")
815         n8 = Node("n8")
816         n9 = Node("n9")
817         n10 = Node("n10")
818
819         n6.side_effects = [ n8 ]
820         n7.side_effects = [ n9, n10 ]
821
822         tm = SCons.Taskmaster.Taskmaster([n6, n7])
823         t = tm.next_task()
824         # More bogus reaching in and setting the targets.
825         n6.get_executor().targets = [n6, n7]
826         t.prepare()
827         assert n6.prepared
828         assert n7.prepared
829         assert n8.prepared
830         assert n9.prepared
831         assert n10.prepared
832
833         # Make sure we call an Executor's prepare() method.
834         class ExceptionExecutor:
835             def prepare(self):
836                 raise Exception, "Executor.prepare() exception"
837             def get_all_targets(self):
838                 return self.nodes
839             def get_all_children(self):
840                 result = []
841                 for node in self.nodes:
842                     result.extend(node.children())
843                 return result
844             def get_all_prerequisites(self):
845                 return []
846             def get_action_side_effects(self):
847                 return []
848
849         n11 = Node("n11")
850         n11.executor = ExceptionExecutor()
851         n11.executor.nodes = [n11]
852         tm = SCons.Taskmaster.Taskmaster([n11])
853         t = tm.next_task()
854         try:
855             t.prepare()
856         except Exception, e:
857             assert str(e) == "Executor.prepare() exception", e
858         else:
859             raise AssertionError, "did not catch expected exception"
860
861     def test_execute(self):
862         """Test executing a task
863         """
864         global built_text
865         global cache_text
866
867         n1 = Node("n1")
868         tm = SCons.Taskmaster.Taskmaster([n1])
869         t = tm.next_task()
870         t.execute()
871         assert built_text == "n1 built", built_text
872
873         def raise_UserError():
874             raise SCons.Errors.UserError
875         n2 = Node("n2")
876         n2.build = raise_UserError
877         tm = SCons.Taskmaster.Taskmaster([n2])
878         t = tm.next_task()
879         try:
880             t.execute()
881         except SCons.Errors.UserError:
882             pass
883         else:
884             raise TestFailed, "did not catch expected UserError"
885
886         def raise_BuildError():
887             raise SCons.Errors.BuildError
888         n3 = Node("n3")
889         n3.build = raise_BuildError
890         tm = SCons.Taskmaster.Taskmaster([n3])
891         t = tm.next_task()
892         try:
893             t.execute()
894         except SCons.Errors.BuildError:
895             pass
896         else:
897             raise TestFailed, "did not catch expected BuildError"
898
899         # On a generic (non-BuildError) exception from a Builder,
900         # the target should throw a BuildError exception with the
901         # args set to the exception value, instance, and traceback.
902         def raise_OtherError():
903             raise OtherError
904         n4 = Node("n4")
905         n4.build = raise_OtherError
906         tm = SCons.Taskmaster.Taskmaster([n4])
907         t = tm.next_task()
908         try:
909             t.execute()
910         except SCons.Errors.BuildError, e:
911             assert e.node == n4, e.node
912             assert e.errstr == "OtherError : ", e.errstr
913             assert len(e.exc_info) == 3, e.exc_info
914             exc_traceback = sys.exc_info()[2]
915             assert isinstance(e.exc_info[2], type(exc_traceback)), e.exc_info[2]
916         else:
917             raise TestFailed, "did not catch expected BuildError"
918
919         built_text = None
920         cache_text = []
921         n5 = Node("n5")
922         n6 = Node("n6")
923         n6.cached = 1
924         tm = SCons.Taskmaster.Taskmaster([n5])
925         t = tm.next_task()
926         # This next line is moderately bogus.  We're just reaching
927         # in and setting the targets for this task to an array.  The
928         # "right" way to do this would be to have the next_task() call
929         # set it up by having something that approximates a real Builder
930         # return this list--but that's more work than is probably
931         # warranted right now.
932         t.targets = [n5, n6]
933         t.execute()
934         assert built_text == "n5 built", built_text
935         assert cache_text == [], cache_text
936
937         built_text = None
938         cache_text = []
939         n7 = Node("n7")
940         n8 = Node("n8")
941         n7.cached = 1
942         n8.cached = 1
943         tm = SCons.Taskmaster.Taskmaster([n7])
944         t = tm.next_task()
945         # This next line is moderately bogus.  We're just reaching
946         # in and setting the targets for this task to an array.  The
947         # "right" way to do this would be to have the next_task() call
948         # set it up by having something that approximates a real Builder
949         # return this list--but that's more work than is probably
950         # warranted right now.
951         t.targets = [n7, n8]
952         t.execute()
953         assert built_text is None, built_text
954         assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
955
956     def test_exception(self):
957         """Test generic Taskmaster exception handling
958
959         """
960         n1 = Node("n1")
961         tm = SCons.Taskmaster.Taskmaster([n1])
962         t  = tm.next_task()
963
964         t.exception_set((1, 2))
965         exc_type, exc_value = t.exception
966         assert exc_type == 1, exc_type
967         assert exc_value == 2, exc_value
968
969         t.exception_set(3)
970         assert t.exception == 3
971
972         try: 1/0
973         except: pass
974         t.exception_set(None)
975         exc_type, exc_value, exc_tb = t.exception
976         assert exc_type is ZeroDivisionError, exc_type
977         exception_values = [
978             "integer division or modulo",
979             "integer division or modulo by zero",
980         ]
981         assert str(exc_value) in exception_values, exc_value
982
983         class Exception1(Exception):
984             pass
985
986         t.exception_set((Exception1, None))
987         try:
988             t.exception_raise()
989         except:
990             exc_type, exc_value = sys.exc_info()[:2]
991             assert exc_type == Exception1, exc_type
992             assert str(exc_value) == '', exc_value
993         else:
994             assert 0, "did not catch expected exception"
995
996         class Exception2(Exception):
997             pass
998
999         t.exception_set((Exception2, "xyzzy"))
1000         try:
1001             t.exception_raise()
1002         except:
1003             exc_type, exc_value = sys.exc_info()[:2]
1004             assert exc_type == Exception2, exc_type
1005             assert str(exc_value) == "xyzzy", exc_value
1006         else:
1007             assert 0, "did not catch expected exception"
1008
1009         class Exception3(Exception):
1010             pass
1011
1012         try:
1013             1/0
1014         except:
1015             tb = sys.exc_info()[2]
1016         t.exception_set((Exception3, "arg", tb))
1017         try:
1018             t.exception_raise()
1019         except:
1020             exc_type, exc_value, exc_tb = sys.exc_info()
1021             assert exc_type == Exception3, exc_type
1022             assert str(exc_value) == "arg", exc_value
1023             import traceback
1024             x = traceback.extract_tb(tb)[-1]
1025             y = traceback.extract_tb(exc_tb)[-1]
1026             assert x == y, "x = %s, y = %s" % (x, y)
1027         else:
1028             assert 0, "did not catch expected exception"
1029
1030     def test_postprocess(self):
1031         """Test postprocessing targets to give them a chance to clean up
1032         """
1033         n1 = Node("n1")
1034         tm = SCons.Taskmaster.Taskmaster([n1])
1035
1036         t = tm.next_task()
1037         assert not n1.postprocessed
1038         t.postprocess()
1039         assert n1.postprocessed
1040
1041         n2 = Node("n2")
1042         n3 = Node("n3")
1043         tm = SCons.Taskmaster.Taskmaster([n2, n3])
1044
1045         assert not n2.postprocessed
1046         assert not n3.postprocessed
1047         t = tm.next_task()
1048         t.postprocess()
1049         assert n2.postprocessed
1050         assert not n3.postprocessed
1051         t = tm.next_task()
1052         t.postprocess()
1053         assert n2.postprocessed
1054         assert n3.postprocessed
1055
1056     def test_trace(self):
1057         """Test Taskmaster tracing
1058         """
1059         import io
1060
1061         trace = io.StringIO()
1062         n1 = Node("n1")
1063         n2 = Node("n2")
1064         n3 = Node("n3", [n1, n2])
1065         tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1066         t = tm.next_task()
1067         t.prepare()
1068         t.execute()
1069         t.postprocess()
1070         n1.set_state(SCons.Node.executed)
1071         t = tm.next_task()
1072         t.prepare()
1073         t.execute()
1074         t.postprocess()
1075         n2.set_state(SCons.Node.executed)
1076         t = tm.next_task()
1077         t.prepare()
1078         t.execute()
1079         t.postprocess()
1080         t = tm.next_task()
1081         assert t is None
1082
1083         value = trace.getvalue()
1084         expect = """\
1085
1086 Taskmaster: Looking for a node to evaluate
1087 Taskmaster:     Considering node <no_state   0   'n1'> and its children:
1088 Taskmaster: Evaluating <pending    0   'n1'>
1089
1090 Task.make_ready_current(): node <pending    0   'n1'>
1091 Task.prepare():      node <executing  0   'n1'>
1092 Task.execute():      node <executing  0   'n1'>
1093 Task.postprocess():  node <executing  0   'n1'>
1094
1095 Taskmaster: Looking for a node to evaluate
1096 Taskmaster:     Considering node <executed   0   'n1'> and its children:
1097 Taskmaster:        already handled (executed)
1098 Taskmaster:     Considering node <no_state   0   'n3'> and its children:
1099 Taskmaster:        <executed   0   'n1'>
1100 Taskmaster:        <no_state   0   'n2'>
1101 Taskmaster:      adjusted ref count: <pending    1   'n3'>, child 'n2'
1102 Taskmaster:     Considering node <no_state   0   'n2'> and its children:
1103 Taskmaster: Evaluating <pending    0   'n2'>
1104
1105 Task.make_ready_current(): node <pending    0   'n2'>
1106 Task.prepare():      node <executing  0   'n2'>
1107 Task.execute():      node <executing  0   'n2'>
1108 Task.postprocess():  node <executing  0   'n2'>
1109 Task.postprocess():  removing <executing  0   'n2'>
1110 Task.postprocess():  adjusted parent ref count <pending    0   'n3'>
1111
1112 Taskmaster: Looking for a node to evaluate
1113 Taskmaster:     Considering node <pending    0   'n3'> and its children:
1114 Taskmaster:        <executed   0   'n1'>
1115 Taskmaster:        <executed   0   'n2'>
1116 Taskmaster: Evaluating <pending    0   'n3'>
1117
1118 Task.make_ready_current(): node <pending    0   'n3'>
1119 Task.prepare():      node <executing  0   'n3'>
1120 Task.execute():      node <executing  0   'n3'>
1121 Task.postprocess():  node <executing  0   'n3'>
1122
1123 Taskmaster: Looking for a node to evaluate
1124 Taskmaster: No candidate anymore.
1125
1126 """
1127         assert value == expect, value
1128
1129
1130
1131 if __name__ == "__main__":
1132     suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1133     if not unittest.TextTestRunner().run(suite).wasSuccessful():
1134         sys.exit(1)
1135
1136 # Local Variables:
1137 # tab-width:4
1138 # indent-tabs-mode:nil
1139 # End:
1140 # vim: set expandtab tabstop=4 shiftwidth=4: