f3cb5f02a863bf8e95fc553edd3b10be53eecedf
[scons.git] / src / engine / SCons / TaskmasterTests.py
1 #
2 # __COPYRIGHT__
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 #
23
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
25
26 import copy
27 import sys
28 import unittest
29
30 import SCons.Taskmaster
31 import SCons.Errors
32
33
34 built_text = None
35 cache_text = []
36 visited_nodes = []
37 executed = None
38 scan_called = 0
39
40 class Node:
41     def __init__(self, name, kids = [], scans = []):
42         self.name = name
43         self.kids = kids
44         self.scans = scans
45         self.cached = 0
46         self.scanned = 0
47         self.scanner = None
48         self.targets = [self]
49         class Builder:
50             def targets(self, node):
51                 return node.targets
52         self.builder = Builder()
53         self.bsig = None
54         self.csig = None
55         self.state = SCons.Node.no_state
56         self.prepared = None
57         self.waiting_parents = []
58         self.side_effect = 0
59         self.side_effects = []
60         self.alttargets = []
61         self.postprocessed = None
62         self._bsig_val = None
63         self._current_val = 0
64
65     def disambiguate(self):
66         return self
67
68     def retrieve_from_cache(self):
69         global cache_text
70         if self.cached:
71             cache_text.append(self.name + " retrieved")
72         return self.cached
73
74     def build(self):
75         global built_text
76         built_text = self.name + " built"
77
78     def has_builder(self):
79         return not self.builder is None
80
81     def is_derived(self):
82         return self.has_builder or self.side_effect
83
84     def alter_targets(self):
85         return self.alttargets, None
86
87     def built(self):
88         global built_text
89         built_text = built_text + " really"
90
91     def visited(self):
92         global visited_nodes
93         visited_nodes.append(self.name)
94
95     def prepare(self):
96         self.prepared = 1
97
98     def children(self):
99         if not self.scanned:
100             self.scan()
101             self.scanned = 1
102         return self.kids
103
104     def scan(self):
105         global scan_called
106         scan_called = scan_called + 1
107         self.kids = self.kids + self.scans
108         self.scans = []
109
110     def scanner_key(self):
111         return self.name
112   
113     def add_to_waiting_parents(self, node):
114         self.waiting_parents.append(node)
115   
116     def call_for_all_waiting_parents(self, func):
117         func(self)
118         for parent in self.waiting_parents:
119             parent.call_for_all_waiting_parents(func)
120
121     def get_state(self):
122         return self.state
123
124     def set_state(self, state):
125         self.state = state
126
127     def set_bsig(self, bsig):
128         self.bsig = bsig
129
130     def set_csig(self, csig):
131         self.csig = csig
132
133     def store_csig(self):
134         pass
135
136     def store_bsig(self):
137         pass
138
139     def calculator(self):
140         class Calc:
141             def bsig(self, node):
142                 return node._bsig_val
143             def current(self, node, sig):
144                 return node._current_val
145         return Calc()
146
147     def current(self, calc=None):
148         if calc is None:
149             calc = self.calculator()
150         return calc.current(self, calc.bsig(self))
151     
152     def depends_on(self, nodes):
153         for node in nodes:
154             if node in self.kids:
155                 return 1
156         return 0
157
158     def __str__(self):
159         return self.name
160
161     def postprocess(self):
162         self.postprocessed = 1
163
164 class OtherError(Exception):
165     pass
166
167 class MyException(Exception):
168     pass
169
170
171 class TaskmasterTestCase(unittest.TestCase):
172
173     def test_next_task(self):
174         """Test fetching the next task
175         """
176         global built_text
177
178         n1 = Node("n1")
179         tm = SCons.Taskmaster.Taskmaster([n1, n1])
180         t = tm.next_task()
181         t.prepare()
182         t.execute()
183         t = tm.next_task()
184         assert t == None
185
186         n1 = Node("n1")
187         n2 = Node("n2")
188         n3 = Node("n3", [n1, n2])
189         
190         tm = SCons.Taskmaster.Taskmaster([n3])
191
192         t = tm.next_task()
193         t.prepare()
194         t.execute()
195         assert built_text == "n1 built", built_text
196         t.executed()
197
198         t = tm.next_task()
199         t.prepare()
200         t.execute()
201         assert built_text == "n2 built", built_text
202         t.executed()
203
204         t = tm.next_task()
205         t.prepare()
206         t.execute()
207         assert built_text == "n3 built", built_text
208         t.executed()
209
210         assert tm.next_task() == None
211
212         built_text = "up to date: "
213         top_node = n3
214
215         class MyTask(SCons.Taskmaster.Task):
216             def execute(self):
217                 global built_text
218                 if self.targets[0].get_state() == SCons.Node.up_to_date:
219                     if self.top:
220                         built_text = self.targets[0].name + " up-to-date top"
221                     else:
222                         built_text = self.targets[0].name + " up-to-date"
223                 else:
224                     self.targets[0].build()
225
226         n1.set_state(SCons.Node.no_state)
227         n1._current_val = 1
228         n2.set_state(SCons.Node.no_state)
229         n2._current_val = 1
230         n3.set_state(SCons.Node.no_state)
231         n3._current_val = 1
232         tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask)
233
234         t = tm.next_task()
235         t.prepare()
236         t.execute()
237         assert built_text == "n1 up-to-date", built_text
238         t.executed()
239
240         t = tm.next_task()
241         t.prepare()
242         t.execute()
243         assert built_text == "n2 up-to-date", built_text
244         t.executed()
245
246         t = tm.next_task()
247         t.prepare()
248         t.execute()
249         assert built_text == "n3 up-to-date top", built_text
250         t.executed()
251
252         assert tm.next_task() == None
253
254
255         n1 = Node("n1")
256         n2 = Node("n2")
257         n3 = Node("n3", [n1, n2])
258         n4 = Node("n4")
259         n5 = Node("n5", [n3, n4])
260         tm = SCons.Taskmaster.Taskmaster([n5])
261
262         assert not tm.is_blocked()
263         
264         t1 = tm.next_task()
265         assert t1.get_target() == n1
266         assert not tm.is_blocked()
267         
268         t2 = tm.next_task()
269         assert t2.get_target() == n2
270         assert not tm.is_blocked()
271
272         t4 = tm.next_task()
273         assert t4.get_target() == n4
274         assert tm.is_blocked()
275         t4.executed()
276         assert tm.is_blocked()
277         
278         t1.executed()
279         assert tm.is_blocked()
280         t2.executed()
281         assert not tm.is_blocked()
282         t3 = tm.next_task()
283         assert t3.get_target() == n3
284         assert tm.is_blocked()
285
286         t3.executed()
287         assert not tm.is_blocked()
288         t5 = tm.next_task()
289         assert t5.get_target() == n5, t5.get_target()
290         assert tm.is_blocked()  # still executing t5
291         t5.executed()
292         assert not tm.is_blocked()
293
294         assert tm.next_task() == None
295
296         
297         n4 = Node("n4")
298         n4.set_state(SCons.Node.executed)
299         tm = SCons.Taskmaster.Taskmaster([n4])
300         assert tm.next_task() == None
301
302         n1 = Node("n1")
303         n2 = Node("n2", [n1])
304         tm = SCons.Taskmaster.Taskmaster([n2,n2])
305         t = tm.next_task()
306         assert tm.is_blocked()
307         t.executed()
308         assert not tm.is_blocked()
309         t = tm.next_task()
310         assert tm.next_task() == None
311
312
313         n1 = Node("n1")
314         n2 = Node("n2")
315         n3 = Node("n3", [n1], [n2])
316         tm = SCons.Taskmaster.Taskmaster([n3])
317         t = tm.next_task()
318         target = t.get_target()
319         assert target == n1, target
320         t.executed()
321         t = tm.next_task()
322         target = t.get_target()
323         assert target == n2, target
324         t.executed()
325         t = tm.next_task()
326         target = t.get_target()
327         assert target == n3, target
328         t.executed()
329         assert tm.next_task() == None
330
331         n1 = Node("n1")
332         n2 = Node("n2")
333         n3 = Node("n3", [n1, n2])
334         n4 = Node("n4", [n3])
335         n5 = Node("n5", [n3])
336         global scan_called
337         scan_called = 0
338         tm = SCons.Taskmaster.Taskmaster([n4])
339         t = tm.next_task()
340         assert t.get_target() == n1
341         t.executed()
342         t = tm.next_task()
343         assert t.get_target() == n2
344         t.executed()
345         t = tm.next_task()
346         assert t.get_target() == n3
347         t.executed()
348         t = tm.next_task()
349         assert t.get_target() == n4
350         t.executed()
351         assert tm.next_task() == None
352         assert scan_called == 4, scan_called
353
354         tm = SCons.Taskmaster.Taskmaster([n5])
355         t = tm.next_task()
356         assert t.get_target() == n5, t.get_target()
357         t.executed()
358         assert tm.next_task() == None
359         assert scan_called == 5, scan_called
360
361         n1 = Node("n1")
362         n2 = Node("n2")
363         n3 = Node("n3")
364         n4 = Node("n4", [n1,n2,n3])
365         n5 = Node("n5", [n4])
366         n3.side_effect = 1
367         n1.side_effects = n2.side_effects = n3.side_effects = [n4]
368         tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
369         t = tm.next_task()
370         assert t.get_target() == n1
371         assert n4.state == SCons.Node.executing
372         assert tm.is_blocked()
373         t.executed()
374         assert not tm.is_blocked()
375         t = tm.next_task()
376         assert t.get_target() == n2
377         assert tm.is_blocked()
378         t.executed()
379         t = tm.next_task()
380         assert t.get_target() == n3
381         assert tm.is_blocked()
382         t.executed()
383         t = tm.next_task()
384         assert t.get_target() == n4
385         assert tm.is_blocked()
386         t.executed()
387         t = tm.next_task()
388         assert t.get_target() == n5
389         assert tm.is_blocked()  # still executing n5
390         assert not tm.next_task()
391         t.executed()
392         assert not tm.is_blocked()
393
394         n1 = Node("n1")
395         n2 = Node("n2")
396         n3 = Node("n3")
397         n4 = Node("n4", [n1,n2,n3])
398         def reverse(dependencies):
399             dependencies.reverse()
400             return dependencies
401         tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
402         t = tm.next_task()
403         assert t.get_target() == n3, t.get_target()
404         t.executed()
405         t = tm.next_task()
406         assert t.get_target() == n2, t.get_target()
407         t.executed()
408         t = tm.next_task()
409         assert t.get_target() == n1, t.get_target()
410         t.executed()
411         t = tm.next_task()
412         assert t.get_target() == n4, t.get_target()
413         t.executed()
414
415         n5 = Node("n5")
416         n6 = Node("n6")
417         n7 = Node("n7")
418         n6.alttargets = [n7]
419         tm = SCons.Taskmaster.Taskmaster([n5])
420         t = tm.next_task()
421         assert t.get_target() == n5
422         t.executed()
423         tm = SCons.Taskmaster.Taskmaster([n6])
424         t = tm.next_task()
425         assert t.get_target() == n7
426         t.executed()
427         t = tm.next_task()
428         assert t.get_target() == n6
429         t.executed()
430
431         n1 = Node("n1")
432         n2 = Node("n2", [n1])
433         n1.set_state(SCons.Node.failed)
434         tm = SCons.Taskmaster.Taskmaster([n2])
435         assert tm.next_task() is None
436
437         n1 = Node("n1")
438         n2 = Node("n2")
439         n1.targets = [n1, n2]
440         n1._current_val = 1
441         tm = SCons.Taskmaster.Taskmaster([n1])
442         t = tm.next_task()
443         t.executed()
444
445         s = n1.get_state()
446         assert s == SCons.Node.up_to_date, s
447         s = n2.get_state()
448         assert s == SCons.Node.executed, s
449
450
451     def test_make_ready_out_of_date(self):
452         """Test the Task.make_ready() method's list of out-of-date Nodes
453         """
454         ood = []
455         def TaskGen(tm, targets, top, node, ood=ood):
456             class MyTask(SCons.Taskmaster.Task):
457                 def make_ready(self):
458                     SCons.Taskmaster.Task.make_ready(self)
459                     self.ood.extend(self.out_of_date)
460             t = MyTask(tm, targets, top, node)
461             t.ood = ood
462             return t
463
464         n1 = Node("n1")
465         c2 = Node("c2")
466         c2._current_val = 1
467         n3 = Node("n3")
468         c4 = Node("c4")
469         c4._current_val = 1
470         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
471                                          tasker = TaskGen)
472
473         del ood[:]
474         t = tm.next_task()
475         assert ood == [n1], ood
476
477         del ood[:]
478         t = tm.next_task()
479         assert ood == [], ood
480
481         del ood[:]
482         t = tm.next_task()
483         assert ood == [n3], ood
484
485         del ood[:]
486         t = tm.next_task()
487         assert ood == [], ood
488
489
490     def test_make_ready_exception(self):
491         """Test handling exceptions from Task.make_ready()
492         """
493         class MyTask(SCons.Taskmaster.Task):
494             def make_ready(self):
495                 raise MyException, "from make_ready()"
496
497         n1 = Node("n1")
498         tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
499         t = tm.next_task()
500         exc_type, exc_value, exc_tb = t.exception
501         assert exc_type == MyException, repr(exc_type)
502         assert str(exc_value) == "from make_ready()", exc_value
503
504
505     def test_make_ready_all(self):
506         """Test the make_ready_all() method"""
507         class MyTask(SCons.Taskmaster.Task):
508             make_ready = SCons.Taskmaster.Task.make_ready_all
509
510         n1 = Node("n1")
511         c2 = Node("c2")
512         c2._current_val = 1
513         n3 = Node("n3")
514         c4 = Node("c4")
515         c4._current_val = 1
516
517         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4])
518
519         t = tm.next_task()
520         target = t.get_target()
521         assert target is n1, target
522         assert target.state == SCons.Node.executing, target.state
523         t = tm.next_task()
524         target = t.get_target()
525         assert target is c2, target
526         assert target.state == SCons.Node.up_to_date, target.state
527         t = tm.next_task()
528         target = t.get_target()
529         assert target is n3, target
530         assert target.state == SCons.Node.executing, target.state
531         t = tm.next_task()
532         target = t.get_target()
533         assert target is c4, target
534         assert target.state == SCons.Node.up_to_date, target.state
535         t = tm.next_task()
536         assert t is None
537
538         n1 = Node("n1")
539         c2 = Node("c2")
540         n3 = Node("n3")
541         c4 = Node("c4")
542
543         tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4],
544                                          tasker = MyTask)
545
546         t = tm.next_task()
547         target = t.get_target()
548         assert target is n1, target
549         assert target.state == SCons.Node.executing, target.state
550         t = tm.next_task()
551         target = t.get_target()
552         assert target is c2, target
553         assert target.state == SCons.Node.executing, target.state
554         t = tm.next_task()
555         target = t.get_target()
556         assert target is n3, target
557         assert target.state == SCons.Node.executing, target.state
558         t = tm.next_task()
559         target = t.get_target()
560         assert target is c4, target
561         assert target.state == SCons.Node.executing, target.state
562         t = tm.next_task()
563         assert t is None
564
565
566     def test_children_errors(self):
567         """Test errors when fetching the children of a node.
568         """
569         class StopNode(Node):
570             def children(self):
571                 raise SCons.Errors.StopError, "stop!"
572         class ExitNode(Node):
573             def children(self):
574                 sys.exit(77)
575
576         n1 = StopNode("n1")
577         tm = SCons.Taskmaster.Taskmaster([n1])
578         t = tm.next_task()
579         exc_type, exc_value, exc_tb = t.exception
580         assert exc_type == SCons.Errors.StopError, repr(exc_type)
581         assert str(exc_value) == "stop!", exc_value
582
583         n2 = ExitNode("n2")
584         tm = SCons.Taskmaster.Taskmaster([n2])
585         t = tm.next_task()
586         exc_type, exc_value = t.exception
587         assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type)
588         assert exc_value.node == n2, exc_value.node
589         assert exc_value.status == 77, exc_value.status
590
591     def test_cycle_detection(self):
592         """Test detecting dependency cycles
593
594         """
595         n1 = Node("n1")
596         n2 = Node("n2", [n1])
597         n3 = Node("n3", [n2])
598         n1.kids = [n3]
599
600         try:
601             tm = SCons.Taskmaster.Taskmaster([n3])
602             t = tm.next_task()
603         except SCons.Errors.UserError, e:
604             assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
605         else:
606             assert 0
607         
608     def test_is_blocked(self):
609         """Test whether a task is blocked
610
611         Both default and overridden in a subclass.
612         """
613         tm = SCons.Taskmaster.Taskmaster()
614         assert not tm.is_blocked()
615
616         class MyTM(SCons.Taskmaster.Taskmaster):
617             def _find_next_ready_node(self):
618                 self.ready = 1
619         tm = MyTM()
620         assert not tm.is_blocked()
621
622         class MyTM(SCons.Taskmaster.Taskmaster):
623             def _find_next_ready_node(self):
624                 self.ready = None
625                 self.pending = []
626                 self.executing = []
627         tm = MyTM()
628         assert not tm.is_blocked()
629
630         class MyTM(SCons.Taskmaster.Taskmaster):
631             def _find_next_ready_node(self):
632                 self.ready = None
633                 self.pending = [1]
634         tm = MyTM()
635         assert tm.is_blocked()
636
637         class MyTM(SCons.Taskmaster.Taskmaster):
638             def _find_next_ready_node(self):
639                 self.ready = None
640                 self.executing = [1]
641         tm = MyTM()
642         assert tm.is_blocked()
643
644     def test_next_top_level_candidate(self):
645         """Test the next_top_level_candidate() method
646         """
647         n1 = Node("n1")
648         n2 = Node("n2", [n1])
649         n3 = Node("n3", [n2])
650
651         tm = SCons.Taskmaster.Taskmaster([n3])
652         t = tm.next_task()
653         assert tm.executing == [n1], tm.executing
654         t.fail_stop()
655         assert t.targets == [n3], t.targets
656         assert t.top == 1, t.top
657
658     def test_stop(self):
659         """Test the stop() method
660
661         Both default and overridden in a subclass.
662         """
663         global built_text
664
665         n1 = Node("n1")
666         n2 = Node("n2")
667         n3 = Node("n3", [n1, n2])
668         
669         tm = SCons.Taskmaster.Taskmaster([n3])
670         t = tm.next_task()
671         t.prepare()
672         t.execute()
673         assert built_text == "n1 built", built_text
674         t.executed()
675         assert built_text == "n1 built really", built_text
676
677         tm.stop()
678         assert tm.next_task() is None
679
680         class MyTM(SCons.Taskmaster.Taskmaster):
681             def stop(self):
682                 global built_text
683                 built_text = "MyTM.stop()"
684                 SCons.Taskmaster.Taskmaster.stop(self)
685
686         n1 = Node("n1")
687         n2 = Node("n2")
688         n3 = Node("n3", [n1, n2])
689
690         built_text = None
691         tm = MyTM([n3])
692         tm.next_task().execute()
693         assert built_text == "n1 built"
694
695         tm.stop()
696         assert built_text == "MyTM.stop()"
697         assert tm.next_task() is None
698
699     def test_failed(self):
700         """Test when a task has failed
701         """
702         n1 = Node("n1")
703         tm = SCons.Taskmaster.Taskmaster([n1])
704         t = tm.next_task()
705         assert tm.executing == [n1], tm.executing
706         tm.failed(n1)
707         assert tm.executing == [], tm.executing
708
709     def test_executed(self):
710         """Test when a task has been executed
711         """
712         global built_text
713         global visited_nodes
714
715         n1 = Node("n1")
716         tm = SCons.Taskmaster.Taskmaster([n1])
717         t = tm.next_task()
718         built_text = "xxx"
719         visited_nodes = []
720         n1.set_state(SCons.Node.executing)
721
722         t.executed()
723
724         s = n1.get_state()
725         assert s == SCons.Node.executed, s
726         assert built_text == "xxx really", built_text
727         assert visited_nodes == [], visited_nodes
728
729         n2 = Node("n2")
730         tm = SCons.Taskmaster.Taskmaster([n2])
731         t = tm.next_task()
732         built_text = "should_not_change"
733         visited_nodes = []
734         n2.set_state(None)
735
736         t.executed()
737
738         s = n2.get_state()
739         assert s is None, s
740         assert built_text == "should_not_change", built_text
741         assert visited_nodes == ['n2'], visited_nodes
742
743         n3 = Node("n3")
744         n4 = Node("n4")
745         n3.targets = [n3, n4]
746         tm = SCons.Taskmaster.Taskmaster([n3])
747         t = tm.next_task()
748         visited_nodes = []
749         n3.set_state(SCons.Node.up_to_date)
750         n4.set_state(SCons.Node.executing)
751
752         t.executed()
753
754         s = n3.get_state()
755         assert s == SCons.Node.up_to_date, s
756         s = n4.get_state()
757         assert s == SCons.Node.executed, s
758         assert visited_nodes == ['n3'], visited_nodes
759
760     def test_prepare(self):
761         """Test preparation of multiple Nodes for a task
762
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         t.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         t.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         t.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     def test_execute(self):
834         """Test executing a task
835
836         """
837         global built_text
838         global cache_text
839
840         n1 = Node("n1")
841         tm = SCons.Taskmaster.Taskmaster([n1])
842         t = tm.next_task()
843         t.execute()
844         assert built_text == "n1 built", built_text
845
846         def raise_UserError():
847             raise SCons.Errors.UserError
848         n2 = Node("n2")
849         n2.build = raise_UserError
850         tm = SCons.Taskmaster.Taskmaster([n2])
851         t = tm.next_task()
852         try:
853             t.execute()
854         except SCons.Errors.UserError:
855             pass
856         else:
857             raise TestFailed, "did not catch expected UserError"
858
859         def raise_BuildError():
860             raise SCons.Errors.BuildError
861         n3 = Node("n3")
862         n3.build = raise_BuildError
863         tm = SCons.Taskmaster.Taskmaster([n3])
864         t = tm.next_task()
865         try:
866             t.execute()
867         except SCons.Errors.BuildError:
868             pass
869         else:
870             raise TestFailed, "did not catch expected BuildError"
871
872         # On a generic (non-BuildError) exception from a Builder,
873         # the target should throw a BuildError exception with the
874         # args set to the exception value, instance, and traceback.
875         def raise_OtherError():
876             raise OtherError
877         n4 = Node("n4")
878         n4.build = raise_OtherError
879         tm = SCons.Taskmaster.Taskmaster([n4])
880         t = tm.next_task()
881         try:
882             t.execute()
883         except SCons.Errors.BuildError, e:
884             assert e.node == n4, e.node
885             assert e.errstr == "Exception", e.errstr
886             assert len(e.args) == 3, `e.args`
887             assert e.args[0] == OtherError, e.args[0]
888             assert isinstance(e.args[1], OtherError), type(e.args[1])
889             exc_traceback = sys.exc_info()[2]
890             assert type(e.args[2]) == type(exc_traceback), e.args[2]
891         else:
892             raise TestFailed, "did not catch expected BuildError"
893
894         built_text = None
895         cache_text = []
896         n5 = Node("n5")
897         n6 = Node("n6")
898         n6.cached = 1
899         tm = SCons.Taskmaster.Taskmaster([n5])
900         t = tm.next_task()
901         # This next line is moderately bogus.  We're just reaching
902         # in and setting the targets for this task to an array.  The
903         # "right" way to do this would be to have the next_task() call
904         # set it up by having something that approximates a real Builder
905         # return this list--but that's more work than is probably
906         # warranted right now.
907         t.targets = [n5, n6]
908         t.execute()
909         assert built_text == "n5 built", built_text
910         assert cache_text == [], cache_text
911
912         built_text = None
913         cache_text = []
914         n7 = Node("n7")
915         n8 = Node("n8")
916         n7.cached = 1
917         n8.cached = 1
918         tm = SCons.Taskmaster.Taskmaster([n7])
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 = [n7, n8]
927         t.execute()
928         assert built_text is None, built_text
929         assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
930
931     def test_exception(self):
932         """Test generic Taskmaster exception handling
933
934         """
935         n1 = Node("n1")
936         tm = SCons.Taskmaster.Taskmaster([n1])
937         t  = tm.next_task()
938
939         t.exception_set((1, 2))
940         exc_type, exc_value = t.exception
941         assert exc_type == 1, exc_type
942         assert exc_value == 2, exc_value
943
944         t.exception_set(3)
945         assert t.exception == 3
946
947         try: 1/0
948         except: pass
949         t.exception_set(None)
950         exc_type, exc_value, exc_tb = t.exception
951         assert exc_type is ZeroDivisionError, exc_type
952         exception_values = [
953             "integer division or modulo",
954             "integer division or modulo by zero",
955         ]
956         assert str(exc_value) in exception_values, exc_value
957
958         t.exception_set(("exception 1", None))
959         try:
960             t.exception_raise()
961         except:
962             exc_type, exc_value = sys.exc_info()[:2]
963             assert exc_type == "exception 1", exc_type
964             assert exc_value is None, exc_value
965         else:
966             assert 0, "did not catch expected exception"
967
968         t.exception_set(("exception 2", "xyzzy"))
969         try:
970             t.exception_raise()
971         except:
972             exc_type, exc_value = sys.exc_info()[:2]
973             assert exc_type == "exception 2", exc_type
974             assert exc_value == "xyzzy", exc_value
975         else:
976             assert 0, "did not catch expected exception"
977
978         try:
979             1/0
980         except:
981             tb = sys.exc_info()[2]
982         t.exception_set(("exception 3", "arg", tb))
983         try:
984             t.exception_raise()
985         except:
986             exc_type, exc_value, exc_tb = sys.exc_info()
987             assert exc_type == 'exception 3', exc_type
988             assert exc_value == "arg", exc_value
989             import traceback
990             x = traceback.extract_tb(tb)[-1]
991             y = traceback.extract_tb(exc_tb)[-1]
992             assert x == y, "x = %s, y = %s" % (x, y)
993         else:
994             assert 0, "did not catch expected exception"
995
996         t.exception_set(("exception 4", "XYZZY"))
997         def fw_exc(exc):
998             raise 'exception_forwarded', exc
999         tm.exception_raise = fw_exc 
1000         try:
1001             t.exception_raise()
1002         except:
1003             exc_type, exc_value = sys.exc_info()[:2]
1004             assert exc_type == 'exception_forwarded', exc_type
1005             assert exc_value[0] == "exception 4", exc_value[0]
1006             assert exc_value[1] == "XYZZY", exc_value[1]
1007         else:
1008             assert 0, "did not catch expected exception"
1009
1010     def test_postprocess(self):
1011         """Test postprocessing targets to give them a chance to clean up
1012         
1013         """
1014         n1 = Node("n1")
1015         tm = SCons.Taskmaster.Taskmaster([n1])
1016
1017         t = tm.next_task()
1018         assert not n1.postprocessed
1019         t.postprocess()
1020         assert n1.postprocessed
1021
1022         n2 = Node("n2")
1023         n3 = Node("n3")
1024         tm = SCons.Taskmaster.Taskmaster([n2, n3])
1025
1026         assert not n2.postprocessed
1027         assert not n3.postprocessed
1028         t = tm.next_task()
1029         t.postprocess()
1030         assert n2.postprocessed
1031         assert not n3.postprocessed
1032         t = tm.next_task()
1033         t.postprocess()
1034         assert n2.postprocessed
1035         assert n3.postprocessed
1036
1037     def test_trace(self):
1038         """Test Taskmaster tracing
1039         """
1040         import StringIO
1041
1042         trace = StringIO.StringIO()
1043         n1 = Node("n1")
1044         n2 = Node("n2")
1045         n3 = Node("n3", [n1, n2])
1046         tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace)
1047         t = tm.next_task()
1048         t.prepare()
1049         t.execute()
1050         n1.set_state(SCons.Node.executed)
1051         t = tm.next_task()
1052         t.prepare()
1053         t.execute()
1054         t = tm.next_task()
1055
1056         value = trace.getvalue()
1057         expect = """\
1058 Taskmaster: 'n1': children:
1059     []
1060     evaluating
1061 Taskmaster: 'n1': already handled
1062 Taskmaster: 'n3': children:
1063     ['n1', 'n2']
1064     waiting on unstarted children:
1065     ['n2']
1066 Taskmaster: 'n2': children:
1067     []
1068     evaluating
1069 Taskmaster: 'n3': children:
1070     ['n1', 'n2']
1071     waiting on unfinished children:
1072     ['n2']
1073 """
1074         assert value == expect, value
1075
1076
1077
1078 if __name__ == "__main__":
1079     suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
1080     if not unittest.TextTestRunner().run(suite).wasSuccessful():
1081         sys.exit(1)