Performance: Use a dictionary, not a list, for a Node's parents. (Stephen Kennedy)
[scons.git] / src / engine / SCons / Node / NodeTests.py
1 #
2 # Copyright (c) 2001, 2002 Steven Knight
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 os
27 import sys
28 import unittest
29
30 import SCons.Errors
31 import SCons.Node
32
33
34
35 built_it = None
36 built_target =  None
37 built_source =  None
38 cycle_detected = None
39
40 class Builder:
41     def execute(self, **kw):
42         global built_it, built_target, built_source
43         built_it = 1
44         built_target = kw['target']
45         built_source = kw['source']
46         return 0
47     def get_contents(self, env):
48         return 7
49
50 class NoneBuilder(Builder):
51     def execute(self, **kw):
52         apply(Builder.execute, (self,), kw)
53         return None
54
55 class ListBuilder(Builder):
56     def __init__(self, *nodes):
57         self.nodes = nodes
58     def execute(self, **kw):
59         if hasattr(self, 'status'):
60             return self.status
61         for n in self.nodes:
62             n.prepare()
63         kw['target'] = self.nodes[0]
64         self.status = apply(Builder.execute, (self,), kw)
65
66 class FailBuilder:
67     def execute(self, **kw):
68         return 1
69
70 class ExceptBuilder:
71     def execute(self, **kw):
72         raise SCons.Errors.BuildError
73
74 class ExceptBuilder2:
75     def execute(self, **kw):
76         raise "foo"
77
78 class Environment:
79     def Dictionary(self, *args):
80         pass
81
82
83
84 class NodeTestCase(unittest.TestCase):
85
86     def test_BuildException(self):
87         """Test throwing an exception on build failure.
88         """
89         node = SCons.Node.Node()
90         node.builder_set(FailBuilder())
91         node.env_set(Environment())
92         try:
93             node.build()
94         except SCons.Errors.BuildError:
95             pass
96         else:
97             raise TestFailed, "did not catch expected BuildError"
98
99         node = SCons.Node.Node()
100         node.builder_set(ExceptBuilder())
101         node.env_set(Environment())
102         try:
103             node.build()
104         except SCons.Errors.BuildError:
105             pass
106         else:
107             raise TestFailed, "did not catch expected BuildError"
108
109         node = SCons.Node.Node()
110         node.builder_set(ExceptBuilder2())
111         node.env_set(Environment())
112         try:
113             node.build()
114         except SCons.Errors.BuildError, e:
115             # On a generic (non-BuildError) exception from a Builder,
116             # the Node should throw a BuildError exception with
117             # the args set to the exception value, type, and traceback.
118             assert len(e.args) == 3, `e.args`
119             assert e.args[0] == 'foo', e.args[0]
120             assert e.args[1] is None
121             assert type(e.args[2]) is type(sys.exc_traceback), e.args[2]
122         else:
123             raise TestFailed, "did not catch expected BuildError"
124
125     def test_build(self):
126         """Test building a node
127         """
128         global built_it
129
130         class MyNode(SCons.Node.Node):
131             def __init__(self, **kw):
132                 apply(SCons.Node.Node.__init__, (self,), kw)
133                 self.prepare_count = 0
134             def __str__(self):
135                 return self.path
136             def prepare(self):
137                 self.prepare_count = self.prepare_count+ 1
138         # Make sure it doesn't blow up if no builder is set.
139         node = MyNode()
140         node.build()
141         assert built_it == None
142
143         node = MyNode()
144         node.builder_set(Builder())
145         node.env_set(Environment())
146         node.path = "xxx"
147         node.sources = ["yyy", "zzz"]
148         node.build()
149         assert built_it
150         assert type(built_target) == type(MyNode()), type(built_target)
151         assert str(built_target) == "xxx", str(built_target)
152         assert built_source == ["yyy", "zzz"], built_source
153
154         built_it = None
155         node = MyNode()
156         node.builder_set(NoneBuilder())
157         node.env_set(Environment())
158         node.path = "qqq"
159         node.sources = ["rrr", "sss"]
160         node.build()
161         assert built_it
162         assert type(built_target) == type(MyNode()), type(built_target)
163         assert str(built_target) == "qqq", str(built_target)
164         assert built_source == ["rrr", "sss"], built_source
165
166         fff = MyNode()
167         ggg = MyNode()
168         lb = ListBuilder(fff, ggg)
169         e = Environment()
170         fff.builder_set(lb)
171         fff.env_set(e)
172         fff.path = "fff"
173         ggg.builder_set(lb)
174         ggg.env_set(e)
175         ggg.path = "ggg"
176         fff.sources = ["hhh", "iii"]
177         ggg.sources = ["hhh", "iii"]
178
179         built_it = None
180         fff.build()
181         assert built_it
182         ggg.build()
183         assert ggg.prepare_count== 1, ggg.prepare_count
184         assert type(built_target) == type(MyNode()), type(built_target)
185         assert str(built_target) == "fff", str(built_target)
186         assert built_source == ["hhh", "iii"], built_source
187
188         delattr(lb, 'status')
189         fff.prepare_count = 0
190         ggg.prepare_count = 0
191
192         built_it = None
193         ggg.build()
194         #assert built_it
195         fff.build()
196         assert fff.prepare_count== 1, fff.prepare_count
197         assert type(built_target) == type(MyNode()), type(built_target)
198         assert str(built_target) == "fff", str(built_target)
199         assert built_source == ["hhh", "iii"], built_source
200
201     def test_builder_set(self):
202         """Test setting a Node's Builder
203         """
204         node = SCons.Node.Node()
205         b = Builder()
206         node.builder_set(b)
207         assert node.builder == b
208
209     def test_builder_sig_adapter(self):
210         """Test the node's adapter for builder signatures
211         """
212         node = SCons.Node.Node()
213         node.builder_set(Builder())
214         node.env_set(Environment())
215         c = node.builder_sig_adapter().get_contents()
216         assert c == 7, c
217
218     def test_current(self):
219         """Test the default current() method
220         """
221         node = SCons.Node.Node()
222         assert node.current() is None
223
224     def test_env_set(self):
225         """Test setting a Node's Environment
226         """
227         node = SCons.Node.Node()
228         e = Environment()
229         node.env_set(e)
230         assert node.env == e
231
232     def test_set_bsig(self):
233         """Test setting a Node's signature
234         """
235         node = SCons.Node.Node()
236         node.set_bsig('www')
237         assert node.bsig == 'www'
238
239     def test_get_bsig(self):
240         """Test fetching a Node's signature
241         """
242         node = SCons.Node.Node()
243         node.set_bsig('xxx')
244         assert node.get_bsig() == 'xxx'
245
246     def test_set_csig(self):
247         """Test setting a Node's signature
248         """
249         node = SCons.Node.Node()
250         node.set_csig('yyy')
251         assert node.csig == 'yyy'
252
253     def test_get_csig(self):
254         """Test fetching a Node's signature
255         """
256         node = SCons.Node.Node()
257         node.set_csig('zzz')
258         assert node.get_csig() == 'zzz'
259
260     def test_store_sigs(self):
261         """Test calling the method to store signatures
262         """
263         node = SCons.Node.Node()
264         node.store_sigs()
265
266     def test_set_precious(self):
267         """Test setting a Node's precious value
268         """
269         node = SCons.Node.Node()
270         node.set_precious()
271         assert node.precious
272         node.set_precious(7)
273         assert node.precious == 7
274
275     def test_add_dependency(self):
276         """Test adding dependencies to a Node's list.
277         """
278         node = SCons.Node.Node()
279         assert node.depends == []
280
281         zero = SCons.Node.Node()
282         try:
283             node.add_dependency(zero)
284         except TypeError:
285             pass
286         else:
287             assert 0
288
289         one = SCons.Node.Node()
290         two = SCons.Node.Node()
291         three = SCons.Node.Node()
292         four = SCons.Node.Node()
293
294         node.add_dependency([one])
295         assert node.depends == [one]
296         node.add_dependency([two, three])
297         assert node.depends == [one, two, three]
298         node.add_dependency([three, four, one])
299         assert node.depends == [one, two, three, four]
300
301         assert zero.get_parents() == []
302         assert one.get_parents() == [node]
303         assert two.get_parents() == [node]
304         assert three.get_parents() == [node]
305         assert four.get_parents() == [node]
306
307
308     def test_add_source(self):
309         """Test adding sources to a Node's list.
310         """
311         node = SCons.Node.Node()
312         assert node.sources == []
313
314         zero = SCons.Node.Node()
315         try:
316             node.add_source(zero)
317         except TypeError:
318             pass
319         else:
320             assert 0
321
322         one = SCons.Node.Node()
323         two = SCons.Node.Node()
324         three = SCons.Node.Node()
325         four = SCons.Node.Node()
326
327         node.add_source([one])
328         assert node.sources == [one]
329         node.add_source([two, three])
330         assert node.sources == [one, two, three]
331         node.add_source([three, four, one])
332         assert node.sources == [one, two, three, four]
333
334         assert zero.get_parents() == []
335         assert one.get_parents() == [node]
336         assert two.get_parents() == [node]
337         assert three.get_parents() == [node]
338         assert four.get_parents() == [node]
339
340     def test_add_implicit(self):
341         """Test adding implicit (scanned) dependencies to a Node's list.
342         """
343         node = SCons.Node.Node()
344         assert node.implicit == {}
345
346         zero = SCons.Node.Node()
347         try:
348             node.add_source(zero)
349         except TypeError:
350             pass
351         else:
352             assert 0
353
354         one = SCons.Node.Node()
355         two = SCons.Node.Node()
356         three = SCons.Node.Node()
357         four = SCons.Node.Node()
358
359         node.add_implicit([one], 1)
360         assert node.implicit[1] == [one]
361         node.add_implicit([two, three], 1)
362         assert node.implicit[1] == [one, two, three]
363         node.add_implicit([three, four, one], 1)
364         assert node.implicit[1] == [one, two, three, four]
365
366         assert zero.get_parents() == []
367         assert one.get_parents() == [node]
368         assert two.get_parents() == [node]
369         assert three.get_parents() == [node]
370         assert four.get_parents() == [node]
371
372         node.add_implicit([one], 2)
373         node.add_implicit([two, three], 3)
374         node.add_implicit([three, four, one], 4)
375
376         assert node.implicit[1] == [one, two, three, four]
377         assert node.implicit[2] == [one]
378         assert node.implicit[3] == [two, three]
379         assert node.implicit[4] == [three, four, one]
380
381     def test_add_ignore(self):
382         """Test adding files whose dependencies should be ignored.
383         """
384         node = SCons.Node.Node()
385         assert node.ignore == []
386
387         zero = SCons.Node.Node()
388         try:
389             node.add_ignore(zero)
390         except TypeError:
391             pass
392         else:
393             assert 0
394
395         one = SCons.Node.Node()
396         two = SCons.Node.Node()
397         three = SCons.Node.Node()
398         four = SCons.Node.Node()
399
400         node.add_ignore([one])
401         assert node.ignore == [one]
402         node.add_ignore([two, three])
403         assert node.ignore == [one, two, three]
404         node.add_ignore([three, four, one])
405         assert node.ignore == [one, two, three, four]
406
407         assert zero.get_parents() == []
408         assert one.get_parents() == [node]
409         assert two.get_parents() == [node]
410         assert three.get_parents() == [node]
411         assert four.get_parents() == [node]
412
413     def test_scan(self):
414         """Test Scanner functionality"""
415         class DummyScanner:
416             pass
417         ds=DummyScanner()
418         node = SCons.Node.Node()
419         assert node.scanners == [], node.scanners
420         node.scanner_set(ds)
421         assert node.scanners == [ ds ], node.scanners
422         node.scan()
423         assert node.scanned[ds] == 1, node.scanned
424
425     def test_children(self):
426         """Test fetching the non-ignored "children" of a Node.
427         """
428         node = SCons.Node.Node()
429         one = SCons.Node.Node()
430         two = SCons.Node.Node()
431         three = SCons.Node.Node()
432         four = SCons.Node.Node()
433         five = SCons.Node.Node()
434         six = SCons.Node.Node()
435
436         node.add_source([one, two, three])
437         node.add_dependency([four, five, six])
438         node.add_ignore([two, five])
439         kids = node.children()
440         assert len(kids) == 4
441         assert one in kids
442         assert not two in kids
443         assert three in kids
444         assert four in kids
445         assert not five in kids
446         assert six in kids
447
448     def test_all_children(self):
449         """Test fetching all the "children" of a Node.
450         """
451         node = SCons.Node.Node()
452         one = SCons.Node.Node()
453         two = SCons.Node.Node()
454         three = SCons.Node.Node()
455         four = SCons.Node.Node()
456         five = SCons.Node.Node()
457         six = SCons.Node.Node()
458
459         node.add_source([one, two, three])
460         node.add_dependency([four, five, six])
461         node.add_ignore([two, five])
462         kids = node.all_children()
463         assert len(kids) == 6
464         assert one in kids
465         assert two in kids
466         assert three in kids
467         assert four in kids
468         assert five in kids
469         assert six in kids
470
471     def test_state(self):
472         """Test setting and getting the state of a node
473         """
474         node = SCons.Node.Node()
475         assert node.get_state() == None
476         node.set_state(SCons.Node.executing)
477         assert node.get_state() == SCons.Node.executing
478         assert SCons.Node.pending < SCons.Node.executing
479         assert SCons.Node.executing < SCons.Node.up_to_date
480         assert SCons.Node.up_to_date < SCons.Node.executed
481         assert SCons.Node.executed < SCons.Node.failed
482
483     def test_walker(self):
484         """Test walking a Node tree.
485         """
486
487         class MyNode(SCons.Node.Node):
488             def __init__(self, name):
489                 SCons.Node.Node.__init__(self)
490                 self.name = name
491
492         n1 = MyNode("n1")
493
494         nw = SCons.Node.Walker(n1)
495         assert not nw.is_done()
496         assert nw.next().name ==  "n1"
497         assert nw.is_done()
498         assert nw.next() == None
499
500         n2 = MyNode("n2")
501         n3 = MyNode("n3")
502         n1.add_source([n2, n3])
503
504         nw = SCons.Node.Walker(n1)
505         assert nw.next().name ==  "n2"
506         assert nw.next().name ==  "n3"
507         assert nw.next().name ==  "n1"
508         assert nw.next() == None
509
510         n4 = MyNode("n4")
511         n5 = MyNode("n5")
512         n6 = MyNode("n6")
513         n7 = MyNode("n7")
514         n2.add_source([n4, n5])
515         n3.add_dependency([n6, n7])
516
517         nw = SCons.Node.Walker(n1)
518         assert nw.next().name ==  "n4"
519         assert nw.next().name ==  "n5"
520         assert nw.history.has_key(n2)
521         assert nw.next().name ==  "n2"
522         assert nw.next().name ==  "n6"
523         assert nw.next().name ==  "n7"
524         assert nw.history.has_key(n3)
525         assert nw.next().name ==  "n3"
526         assert nw.history.has_key(n1)
527         assert nw.next().name ==  "n1"
528         assert nw.next() == None
529
530         n8 = MyNode("n8")
531         n8.add_dependency([n3])
532         n7.add_dependency([n8])
533
534         def cycle(node, stack):
535             global cycle_detected
536             cycle_detected = 1
537
538         global cycle_detected
539
540         nw = SCons.Node.Walker(n3, cycle_func = cycle)
541         n = nw.next()
542         assert n.name == "n6", n.name
543         n = nw.next()
544         assert n.name == "n8", n.name
545         assert cycle_detected
546         cycle_detected = None
547         n = nw.next()
548         assert n.name == "n7", n.name
549         n = nw.next()
550         assert nw.next() == None
551
552     def test_children_are_executed(self):
553         n1 = SCons.Node.Node()
554         n2 = SCons.Node.Node()
555         n3 = SCons.Node.Node()
556         n4 = SCons.Node.Node()
557
558         n4.add_source([n3])
559         n3.add_source([n1, n2])
560
561         assert not n4.children_are_executed()
562         assert not n3.children_are_executed()
563         assert n2.children_are_executed()
564         assert n1.children_are_executed()
565
566         n1.set_state(SCons.Node.executed)
567         assert not n4.children_are_executed()
568         assert not n3.children_are_executed()
569         assert n2.children_are_executed()
570         assert n1.children_are_executed()
571
572         n2.set_state(SCons.Node.executed)
573         assert not n4.children_are_executed()
574         assert n3.children_are_executed()
575         assert n2.children_are_executed()
576         assert n1.children_are_executed()
577
578         n3.set_state(SCons.Node.executed)
579         assert n4.children_are_executed()
580         assert n3.children_are_executed()
581         assert n2.children_are_executed()
582         assert n1.children_are_executed()
583
584     def test_rescan(self):
585         """Test that built nodes are rescanned."""
586         class DummyScanner:
587             pass
588         
589         class TestNode(SCons.Node.Node):
590             def scan(self):
591                 for scn in self.scanners:
592                     if not self.scanned.has_key(scn):
593                         n=SCons.Node.Node()
594                         n.scanner_set(scn)
595                         self.add_implicit([ n ], scn)
596                     self.scanned[scn] = 1
597         tn=TestNode()
598         tn.builder_set(Builder())
599         tn.env_set(Environment())
600         ds = DummyScanner()
601         tn.scanner_set(ds)
602         tn.scan()
603         map(lambda x: x.scan(), tn.depends)
604         assert tn.scanned[ds]
605         assert len(tn.implicit[ds]) == 1, tn.implicit
606         tn.build()
607         assert len(tn.implicit[ds]) == 2, tn.implicit
608         for dep in tn.implicit[ds]:
609             assert dep.scanned[ds] == 1
610
611 if __name__ == "__main__":
612     suite = unittest.makeSuite(NodeTestCase, 'test_')
613     if not unittest.TextTestRunner().run(suite).wasSuccessful():
614         sys.exit(1)