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