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