Prepare side-effect nodes before building, too. (Charles Crain)
[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 sys
27 import unittest
28
29 import SCons.Taskmaster
30 import SCons.Errors
31
32
33 built_text = None
34 visited_nodes = []
35 executed = None
36 scan_called = 0
37
38 class Node:
39     def __init__(self, name, kids = [], scans = []):
40         self.name = name
41         self.kids = kids
42         self.scans = scans
43         self.scanned = 0
44         self.scanner = None
45         self.builder = Node.build
46         self.bsig = None
47         self.csig = None
48         self.state = None
49         self.prepared = None
50         self.parents = []
51         self.side_effect = 0
52         self.side_effects = []
53         self.alttargets = []
54
55         for kid in kids:
56             kid.parents.append(self)
57
58     def build(self):
59         global built_text
60         built_text = self.name + " built"
61
62     def has_builder(self):
63         return not self.builder is None
64
65     def is_derived(self):
66         return self.has_builder or self.side_effect
67
68     def alter_targets(self):
69         return self.alttargets, None
70
71     def built(self):
72         global built_text
73         built_text = built_text + " really"
74
75     def visited(self):
76         global visited_nodes
77         visited_nodes.append(self.name)
78
79     def prepare(self):
80         self.prepared = 1
81
82     def children(self):
83         if not self.scanned:
84             self.scan()
85             self.scanned = 1
86         return self.kids
87
88     def scan(self):
89         global scan_called
90         scan_called = scan_called + 1
91         self.kids = self.kids + self.scans
92         for scan in self.scans:
93             scan.parents.append(self)
94         self.scans = []
95
96     def scanner_key(self):
97         return self.name
98   
99     def get_parents(self):
100         return self.parents
101
102     def get_state(self):
103         return self.state
104
105     def set_state(self, state):
106         self.state = state
107
108     def set_bsig(self, bsig):
109         self.bsig = bsig
110
111     def set_csig(self, csig):
112         self.csig = csig
113
114     def store_csig(self):
115         pass
116
117     def store_bsig(self):
118         pass
119
120     def current(self, calc):
121         return calc.current(self, calc.bsig(self))
122     
123     def depends_on(self, nodes):
124         for node in nodes:
125             if node in self.kids:
126                 return 1
127         return 0
128
129     def __str__(self):
130         return self.name
131
132
133 class OtherError(Exception):
134     pass
135
136 class MyException(Exception):
137     pass
138
139
140 class TaskmasterTestCase(unittest.TestCase):
141
142     def test_next_task(self):
143         """Test fetching the next task
144         """
145         global built_text
146         
147         n1 = Node("n1")
148         tm = SCons.Taskmaster.Taskmaster([n1, n1])
149         t = tm.next_task()
150         t.prepare()
151         t.execute()
152         t = tm.next_task()
153         assert t == None
154
155         n1 = Node("n1")
156         n2 = Node("n2")
157         n3 = Node("n3", [n1, n2])
158         
159         tm = SCons.Taskmaster.Taskmaster([n3])
160
161         t = tm.next_task()
162         t.prepare()
163         t.execute()
164         assert built_text == "n1 built", built_text
165         t.executed()
166
167         t = tm.next_task()
168         t.prepare()
169         t.execute()
170         assert built_text == "n2 built", built_text
171         t.executed()
172
173         t = tm.next_task()
174         t.prepare()
175         t.execute()
176         assert built_text == "n3 built", built_text
177         t.executed()
178
179         assert tm.next_task() == None
180
181         built_text = "up to date: "
182         top_node = n3
183
184         class MyCalc(SCons.Taskmaster.Calc):
185             def current(self, node, sig):
186                 return 1
187
188         class MyTask(SCons.Taskmaster.Task):
189             def execute(self):
190                 global built_text
191                 if self.targets[0].get_state() == SCons.Node.up_to_date:
192                     if self.top:
193                         built_text = self.targets[0].name + " up-to-date top"
194                     else:
195                         built_text = self.targets[0].name + " up-to-date"
196                 else:
197                     self.targets[0].build()
198
199         n1.set_state(None)
200         n2.set_state(None)
201         n3.set_state(None)
202         tm = SCons.Taskmaster.Taskmaster(targets = [n3],
203                                          tasker = MyTask, calc = MyCalc())
204
205         t = tm.next_task()
206         t.prepare()
207         t.execute()
208         assert built_text == "n1 up-to-date", built_text
209         t.executed()
210
211         t = tm.next_task()
212         t.prepare()
213         t.execute()
214         assert built_text == "n2 up-to-date", built_text
215         t.executed()
216
217         t = tm.next_task()
218         t.prepare()
219         t.execute()
220         assert built_text == "n3 up-to-date top", built_text
221         t.executed()
222
223         assert tm.next_task() == None
224
225
226         n1 = Node("n1")
227         n2 = Node("n2")
228         n3 = Node("n3", [n1, n2])
229         n4 = Node("n4")
230         n5 = Node("n5", [n3, n4])
231         tm = SCons.Taskmaster.Taskmaster([n5])
232
233         assert not tm.is_blocked()
234         
235         t1 = tm.next_task()
236         assert t1.get_target() == n1
237         assert not tm.is_blocked()
238         
239         t2 = tm.next_task()
240         assert t2.get_target() == n2
241         assert not tm.is_blocked()
242
243         t4 = tm.next_task()
244         assert t4.get_target() == n4
245         assert tm.is_blocked()
246         t4.executed()
247         assert tm.is_blocked()
248         
249         t1.executed()
250         assert tm.is_blocked()
251         t2.executed()
252         assert not tm.is_blocked()
253         t3 = tm.next_task()
254         assert t3.get_target() == n3
255         assert tm.is_blocked()
256
257         t3.executed()
258         assert not tm.is_blocked()
259         t5 = tm.next_task()
260         assert t5.get_target() == n5, t5.get_target()
261         assert tm.is_blocked()  # still executing t5
262         t5.executed()
263         assert not tm.is_blocked()
264
265         assert tm.next_task() == None
266
267         
268         n4 = Node("n4")
269         n4.set_state(SCons.Node.executed)
270         tm = SCons.Taskmaster.Taskmaster([n4])
271         assert tm.next_task() == None
272
273         n1 = Node("n1")
274         n2 = Node("n2", [n1])
275         tm = SCons.Taskmaster.Taskmaster([n2,n2])
276         t = tm.next_task()
277         assert tm.is_blocked()
278         t.executed()
279         assert not tm.is_blocked()
280         t = tm.next_task()
281         assert tm.next_task() == None
282
283
284         n1 = Node("n1")
285         n2 = Node("n2")
286         n3 = Node("n3", [n1], [n2])
287         tm = SCons.Taskmaster.Taskmaster([n3])
288         t = tm.next_task()
289         target = t.get_target()
290         assert target == n1, target
291         t.executed()
292         t = tm.next_task()
293         target = t.get_target()
294         assert target == n2, target
295         t.executed()
296         t = tm.next_task()
297         target = t.get_target()
298         assert target == n3, target
299         t.executed()
300         assert tm.next_task() == None
301
302         n1 = Node("n1")
303         n2 = Node("n2")
304         n3 = Node("n3", [n1, n2])
305         n4 = Node("n4", [n3])
306         n5 = Node("n5", [n3])
307         global scan_called
308         scan_called = 0
309         tm = SCons.Taskmaster.Taskmaster([n4])
310         t = tm.next_task()
311         assert t.get_target() == n1
312         t.executed()
313         t = tm.next_task()
314         assert t.get_target() == n2
315         t.executed()
316         t = tm.next_task()
317         assert t.get_target() == n3
318         t.executed()
319         t = tm.next_task()
320         assert t.get_target() == n4
321         t.executed()
322         assert tm.next_task() == None
323         assert scan_called == 4, scan_called
324
325         tm = SCons.Taskmaster.Taskmaster([n5])
326         t = tm.next_task()
327         assert t.get_target() == n5, t.get_target()
328         t.executed()
329         assert tm.next_task() == None
330         assert scan_called == 5, scan_called
331
332         n1 = Node("n1")
333         n2 = Node("n2")
334         n3 = Node("n3")
335         n4 = Node("n4", [n1,n2,n3])
336         n5 = Node("n5", [n4])
337         n3.side_effect = 1
338         n1.side_effects = n2.side_effects = n3.side_effects = [n4]
339         tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
340         t = tm.next_task()
341         assert t.get_target() == n1
342         assert n4.state == SCons.Node.executing
343         assert tm.is_blocked()
344         t.executed()
345         assert not tm.is_blocked()
346         t = tm.next_task()
347         assert t.get_target() == n2
348         assert tm.is_blocked()
349         t.executed()
350         t = tm.next_task()
351         assert t.get_target() == n3
352         assert tm.is_blocked()
353         t.executed()
354         t = tm.next_task()
355         assert t.get_target() == n4
356         assert tm.is_blocked()
357         t.executed()
358         t = tm.next_task()
359         assert t.get_target() == n5
360         assert tm.is_blocked()  # still executing n5
361         assert not tm.next_task()
362         t.executed()
363         assert not tm.is_blocked()
364
365         n1 = Node("n1")
366         n2 = Node("n2")
367         n3 = Node("n3")
368         n4 = Node("n4", [n1,n2,n3])
369         def reverse(dependencies):
370             dependencies.reverse()
371             return dependencies
372         tm = SCons.Taskmaster.Taskmaster([n4], order=reverse)
373         t = tm.next_task()
374         assert t.get_target() == n3, t.get_target()
375         t.executed()
376         t = tm.next_task()
377         assert t.get_target() == n2, t.get_target()
378         t.executed()
379         t = tm.next_task()
380         assert t.get_target() == n1, t.get_target()
381         t.executed()
382         t = tm.next_task()
383         assert t.get_target() == n4, t.get_target()
384         t.executed()
385
386         n5 = Node("n5")
387         n6 = Node("n6")
388         n7 = Node("n7")
389         n6.alttargets = [n7]
390         tm = SCons.Taskmaster.Taskmaster([n5])
391         t = tm.next_task()
392         assert t.get_target() == n5
393         t.executed()
394         tm = SCons.Taskmaster.Taskmaster([n6])
395         t = tm.next_task()
396         assert t.get_target() == n7
397         t.executed()
398
399
400     def test_make_ready_exception(self):
401         """Test handling exceptions from Task.make_ready()
402         """
403         class MyTask(SCons.Taskmaster.Task):
404             def make_ready(self):
405                 raise MyException, "from make_ready()"
406
407         n1 = Node("n1")
408         tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
409         t = tm.next_task()
410         assert isinstance(tm.exc_type, SCons.Errors.TaskmasterException), repr(tm.exc_type)
411         assert tm.exc_value is None, tm.exc_value
412         e = tm.exc_type
413         assert e.type == MyException, e.type
414         assert str(e.value) == "from make_ready()", str(e.value)
415
416
417     def test_children_errors(self):
418         """Test errors when fetching the children of a node.
419         """
420         class StopNode(Node):
421             def children(self):
422                 raise SCons.Errors.StopError, "stop!"
423         class ExitNode(Node):
424             def children(self):
425                 sys.exit(77)
426
427         n1 = StopNode("n1")
428         tm = SCons.Taskmaster.Taskmaster([n1])
429         t = tm.next_task()
430         assert isinstance(tm.exc_type, SCons.Errors.TaskmasterException), repr(tm.exc_type)
431         assert tm.exc_value is None, tm.exc_value
432         e = tm.exc_type
433         assert e.type == SCons.Errors.StopError, e.type
434         assert str(e.value) == "stop!", "Unexpected exc_value `%s'" % e.value
435
436         n2 = ExitNode("n2")
437         tm = SCons.Taskmaster.Taskmaster([n2])
438         t = tm.next_task()
439         assert tm.exc_type == SCons.Errors.ExplicitExit, "Did not record ExplicitExit on node"
440         assert tm.exc_value.node == n2, tm.exc_value.node
441         assert tm.exc_value.status == 77, tm.exc_value.status
442
443     def test_cycle_detection(self):
444         """Test detecting dependency cycles
445
446         """
447         n1 = Node("n1")
448         n2 = Node("n2", [n1])
449         n3 = Node("n3", [n2])
450         n1.kids = [n3]
451         n3.parents.append(n1)
452
453         try:
454             tm = SCons.Taskmaster.Taskmaster([n3])
455             t = tm.next_task()
456         except SCons.Errors.UserError, e:
457             assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e)
458         else:
459             assert 0
460         
461     def test_is_blocked(self):
462         """Test whether a task is blocked
463
464         Both default and overridden in a subclass.
465         """
466         tm = SCons.Taskmaster.Taskmaster()
467         assert not tm.is_blocked()
468
469         class MyTM(SCons.Taskmaster.Taskmaster):
470             def _find_next_ready_node(self):
471                 self.ready = 1
472         tm = MyTM()
473         assert not tm.is_blocked()
474
475         class MyTM(SCons.Taskmaster.Taskmaster):
476             def _find_next_ready_node(self):
477                 self.ready = None
478                 self.pending = []
479                 self.executing = []
480         tm = MyTM()
481         assert not tm.is_blocked()
482
483         class MyTM(SCons.Taskmaster.Taskmaster):
484             def _find_next_ready_node(self):
485                 self.ready = None
486                 self.pending = [1]
487         tm = MyTM()
488         assert tm.is_blocked()
489
490         class MyTM(SCons.Taskmaster.Taskmaster):
491             def _find_next_ready_node(self):
492                 self.ready = None
493                 self.executing = [1]
494         tm = MyTM()
495         assert tm.is_blocked()
496
497     def test_stop(self):
498         """Test the stop() method
499
500         Both default and overridden in a subclass.
501         """
502         global built_text
503
504         n1 = Node("n1")
505         n2 = Node("n2")
506         n3 = Node("n3", [n1, n2])
507         
508         tm = SCons.Taskmaster.Taskmaster([n3])
509         t = tm.next_task()
510         t.prepare()
511         t.execute()
512         assert built_text == "n1 built", built_text
513         t.executed()
514         assert built_text == "n1 built really", built_text
515
516         tm.stop()
517         assert tm.next_task() is None
518
519         class MyTM(SCons.Taskmaster.Taskmaster):
520             def stop(self):
521                 global built_text
522                 built_text = "MyTM.stop()"
523                 SCons.Taskmaster.Taskmaster.stop(self)
524
525         n1 = Node("n1")
526         n2 = Node("n2")
527         n3 = Node("n3", [n1, n2])
528
529         built_text = None
530         tm = MyTM([n3])
531         tm.next_task().execute()
532         assert built_text == "n1 built"
533
534         tm.stop()
535         assert built_text == "MyTM.stop()"
536         assert tm.next_task() is None
537
538     def test_failed(self):
539         """Test when a task has failed
540         """
541         n1 = Node("n1")
542         tm = SCons.Taskmaster.Taskmaster([n1])
543         t = tm.next_task()
544         assert tm.executing == [n1], tm.executing
545         tm.failed(n1)
546         assert tm.executing == [], tm.executing
547
548     def test_executed(self):
549         """Test when a task has been executed
550         """
551         global built_text
552         global visited_nodes
553
554         n1 = Node("n1")
555         tm = SCons.Taskmaster.Taskmaster([n1])
556         t = tm.next_task()
557         built_text = "xxx"
558         visited_nodes = []
559         n1.set_state(SCons.Node.executing)
560         t.executed()
561         s = n1.get_state()
562         assert s == SCons.Node.executed, s
563         assert built_text == "xxx really", built_text
564         assert visited_nodes == [], visited_nodes
565
566         n2 = Node("n2")
567         tm = SCons.Taskmaster.Taskmaster([n2])
568         t = tm.next_task()
569         built_text = "should_not_change"
570         visited_nodes = []
571         n2.set_state(None)
572         t.executed()
573         s = n1.get_state()
574         assert s == SCons.Node.executed, s
575         assert built_text == "should_not_change", built_text
576         assert visited_nodes == ["n2"], visited_nodes
577
578     def test_prepare(self):
579         """Test preparation of multiple Nodes for a task
580
581         """
582         n1 = Node("n1")
583         n2 = Node("n2")
584         tm = SCons.Taskmaster.Taskmaster([n1, n2])
585         t = tm.next_task()
586         # This next line is moderately bogus.  We're just reaching
587         # in and setting the targets for this task to an array.  The
588         # "right" way to do this would be to have the next_task() call
589         # set it up by having something that approximates a real Builder
590         # return this list--but that's more work than is probably
591         # warranted right now.
592         t.targets = [n1, n2]
593         t.prepare()
594         assert n1.prepared
595         assert n2.prepared
596
597         n3 = Node("n3")
598         n4 = Node("n4")
599         tm = SCons.Taskmaster.Taskmaster([n3, n4])
600         t = tm.next_task()
601         # More bogus reaching in and setting the targets.
602         n3.set_state(SCons.Node.up_to_date)
603         t.targets = [n3, n4]
604         t.prepare()
605         assert n3.prepared
606         assert n4.prepared
607
608         # If the Node has had an exception recorded while it was getting
609         # prepared, then prepare() should raise that exception.
610         class MyException(Exception):
611             pass
612
613         built_text = None
614         n5 = Node("n5")
615         tm = SCons.Taskmaster.Taskmaster([n5])
616         tm.exc_type = MyException
617         tm.exc_value = "exception value"
618         t = tm.next_task()
619         exc_caught = None
620         try:
621             t.prepare()
622         except MyException, e:
623             exc_caught = 1
624         except:
625             pass
626         assert exc_caught, "did not catch expected MyException"
627         assert str(e) == "exception value", e
628         assert built_text is None, built_text
629
630         # Regression test, make sure we prepare not only
631         # all targets, but their side effects as well.
632         n6 = Node("n6")
633         n7 = Node("n7")
634         n8 = Node("n8")
635         n9 = Node("n9")
636         n10 = Node("n10")
637
638         n6.side_effects = [ n8 ]
639         n7.side_effects = [ n9, n10 ]
640         
641         tm = SCons.Taskmaster.Taskmaster([n6, n7])
642         t = tm.next_task()
643         # More bogus reaching in and setting the targets.
644         t.targets = [n6, n7]
645         t.prepare()
646         assert n6.prepared
647         assert n7.prepared
648         assert n8.prepared
649         assert n9.prepared
650         assert n10.prepared
651
652     def test_execute(self):
653         """Test executing a task
654
655         """
656         global built_text
657
658         n1 = Node("n1")
659         tm = SCons.Taskmaster.Taskmaster([n1])
660         t = tm.next_task()
661         t.execute()
662         assert built_text == "n1 built", built_text
663
664         def raise_UserError():
665             raise SCons.Errors.UserError
666         n2 = Node("n2")
667         n2.build = raise_UserError
668         tm = SCons.Taskmaster.Taskmaster([n2])
669         t = tm.next_task()
670         try:
671             t.execute()
672         except SCons.Errors.UserError:
673             pass
674         else:
675             raise TestFailed, "did not catch expected UserError"
676
677         def raise_BuildError():
678             raise SCons.Errors.BuildError
679         n3 = Node("n3")
680         n3.build = raise_BuildError
681         tm = SCons.Taskmaster.Taskmaster([n3])
682         t = tm.next_task()
683         try:
684             t.execute()
685         except SCons.Errors.BuildError:
686             pass
687         else:
688             raise TestFailed, "did not catch expected BuildError"
689
690         # On a generic (non-BuildError) exception from a Builder,
691         # the target should throw a BuildError exception with the
692         # args set to the exception value, instance, and traceback.
693         def raise_OtherError():
694             raise OtherError
695         n4 = Node("n4")
696         n4.build = raise_OtherError
697         tm = SCons.Taskmaster.Taskmaster([n4])
698         t = tm.next_task()
699         try:
700             t.execute()
701         except SCons.Errors.BuildError, e:
702             assert e.node == n4, e.node
703             assert e.errstr == "Exception", e.errstr
704             assert len(e.args) == 3, `e.args`
705             assert e.args[0] == OtherError, e.args[0]
706             assert isinstance(e.args[1], OtherError), type(e.args[1])
707             assert type(e.args[2]) == type(sys.exc_traceback), e.args[2]
708         else:
709             raise TestFailed, "did not catch expected BuildError"
710
711     def test_exception(self):
712         """Test generic Taskmaster exception handling
713
714         """
715         n1 = Node("n1")
716         tm = SCons.Taskmaster.Taskmaster([n1])
717
718         tm.exception_set(1, 2)
719         assert tm.exc_type == 1, tm.exc_type
720         assert tm.exc_value == 2, tm.exc_value
721
722         tm.exception_set(3)
723         assert tm.exc_type == 3, tm.exc_type
724         assert tm.exc_value is None, tm.exc_value
725
726         tm.exception_set(None, None)
727         assert tm.exc_type is None, tm.exc_type
728         assert tm.exc_value is None, tm.exc_value
729
730         tm.exception_set("exception 1", None)
731         try:
732             tm.exception_raise()
733         except:
734             assert sys.exc_type == "exception 1", sys.exc_type
735             assert sys.exc_value is None, sys.exc_value
736         else:
737             assert 0, "did not catch expected exception"
738
739         tm.exception_set("exception 2", "xyzzy")
740         try:
741             tm.exception_raise()
742         except:
743             assert sys.exc_type == "exception 2", sys.exc_type
744             assert sys.exc_value == "xyzzy", sys.exc_value
745         else:
746             assert 0, "did not catch expected exception"
747
748
749
750 if __name__ == "__main__":
751     suite = unittest.makeSuite(TaskmasterTestCase, 'test_')
752     if not unittest.TextTestRunner().run(suite).wasSuccessful():
753         sys.exit(1)