7e1cc40038c040c2cea12715750443742fabec92
[scons.git] / src / engine / SCons / Sig / SigTests.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 unittest
27 import TestCmd
28 import SCons.Sig
29 import SCons.Sig.MD5
30 import SCons.Sig.TimeStamp
31 import sys
32
33
34 class DummyFile:
35     """A class that simulates a file for testing purposes"""
36     def __init__(self, path, contents, timestamp, builder):
37         self.path = path
38         self.contents = contents
39         self.timestamp = timestamp
40         self.builder = builder
41
42     def modify(self, contents, timestamp):
43         self.contents = contents
44         self.timestamp = timestamp
45
46 class DummyNode:
47     """A node workalike for testing purposes"""
48
49     def __init__(self, file):
50         self.file = file
51         self.path = file.path
52         self.builder = file.builder
53         self.depends = []
54         self.ignore = []
55         self.use_signature = 1
56         self.bsig = None
57         self.csig = None
58         self.oldtime = 0
59         self.oldbsig = 0
60         self.oldcsig = 0
61
62     def has_builder(self):
63         return self.builder
64
65     def get_contents(self):
66         # a file that doesn't exist has no contents:
67         assert self.exists()
68
69         return self.file.contents
70
71     def get_timestamp(self):
72         # a file that doesn't exist has no timestamp:
73         assert self.exists()
74
75         return self.file.timestamp
76
77     def exists(self):
78         return not self.file.contents is None
79
80     def cached_exists(self):
81         try:
82             return self.exists_cache
83         except AttributeError:
84             self.exists_cache = self.exists()
85             return self.exists_cache
86
87     def rexists(self):
88         return not self.file.contents is None
89
90     def children(self):
91         return filter(lambda x, i=self.ignore: x not in i,
92                       self.sources + self.depends)
93
94     def all_children(self):
95         return self.sources + self.depends
96
97     def current(self):
98         if not self.exists():
99             return 0
100         return None
101
102     def calc_signature(self, calc):
103         if self.has_builder():
104             return calc.bsig(self)
105         else:
106             return calc.csig(self)
107
108     def set_bsig(self, bsig):
109         self.bsig = bsig
110
111     def get_bsig(self):
112         return self.bsig
113
114     def store_bsig(self):
115         pass
116
117     def set_csig(self, csig):
118         self.csig = csig
119
120     def get_csig(self):
121         return self.csig
122
123     def store_csig(self):
124         pass
125
126     def get_prevsiginfo(self):
127         return (self.oldtime, self.oldbsig, self.oldcsig)
128
129     def get_stored_implicit(self):
130         return None
131
132     def store_csig(self):
133         pass
134
135     def store_bsig(self):
136         pass
137     
138     def store_timestamp(self):
139         pass
140
141     def builder_sig_adapter(self):
142         class Adapter:
143             def get_contents(self):
144                 return 111
145             def get_timestamp(self):
146                 return 222
147         return Adapter()
148
149
150 def create_files(test):
151     args  = [(test.workpath('f1.c'), 'blah blah', 111, 0),     #0
152              (test.workpath('f1'), None, 0, 1),                #1
153              (test.workpath('d1/f1.c'), 'blah blah', 111, 0),  #2
154              (test.workpath('d1/f1.h'), 'blah blah', 111, 0),  #3
155              (test.workpath('d1/f2.c'), 'blah blah', 111, 0),  #4
156              (test.workpath('d1/f3.h'), 'blah blah', 111, 0),  #5
157              (test.workpath('d1/f4.h'), 'blah blah', 111, 0),  #6
158              (test.workpath('d1/f1'), None, 0, 1),             #7
159              (test.workpath('d1/test.c'), 'blah blah', 111, 0),#8
160              (test.workpath('d1/test.o'), None, 0, 1),         #9
161              (test.workpath('d1/test'), None, 0, 1)]           #10
162
163     files = map(lambda x: apply(DummyFile, x), args)
164
165     return files
166
167 def create_nodes(files):
168     nodes = map(DummyNode, files)
169
170     nodes[0].sources = []
171     nodes[1].sources = [nodes[0]]
172     nodes[2].sources = []
173     nodes[3].sources = []
174     nodes[4].sources = []
175     nodes[5].sources = [nodes[6]]
176     nodes[6].sources = [nodes[5]]
177     nodes[7].sources = [nodes[2], nodes[4], nodes[3], nodes[5]]
178     nodes[8].sources = []
179     nodes[9].sources = [nodes[8]]
180     nodes[10].sources = [nodes[9]]
181
182     return nodes
183
184 def current(calc, node):
185     return calc.current(node, node.calc_signature(calc))
186
187 def write(calc, nodes):
188     for node in nodes:
189         node.oldtime = node.file.timestamp
190         node.oldbsig = calc.bsig(node)
191         node.oldcsig = calc.csig(node)
192
193 def clear(nodes):
194     for node in nodes:
195         node.csig = None
196         node.bsig = None
197
198 class SigTestBase:
199
200     def runTest(self):
201
202         test = TestCmd.TestCmd(workdir = '')
203         test.subdir('d1')
204
205         self.files = create_files(test)
206         self.test_initial()
207         self.test_built()
208         self.test_modify()
209         self.test_modify_same_time()
210
211     def test_initial(self):
212
213         nodes = create_nodes(self.files)
214         calc = SCons.Sig.Calculator(self.module)
215
216         for node in nodes:
217             self.failUnless(not current(calc, node),
218                             "node %s should not be current" % node.path)
219
220         # simulate a build:
221         self.files[1].modify('built', 222)
222         self.files[7].modify('built', 222)
223         self.files[9].modify('built', 222)
224         self.files[10].modify('built', 222)
225
226     def test_built(self):
227
228         nodes = create_nodes(self.files)
229
230         calc = SCons.Sig.Calculator(self.module)
231
232         write(calc, nodes)
233
234         for node in nodes:
235             self.failUnless(current(calc, node),
236                             "node %s should be current" % node.path)
237
238     def test_modify(self):
239
240         nodes = create_nodes(self.files)
241
242         calc = SCons.Sig.Calculator(self.module)
243
244         write(calc, nodes)
245
246         #simulate a modification of some files
247         self.files[0].modify('blah blah blah', 333)
248         self.files[3].modify('blah blah blah', 333)
249         self.files[6].modify('blah blah blah', 333)
250         self.files[8].modify('blah blah blah', 333)
251
252         clear(nodes)
253
254         self.failUnless(not current(calc, nodes[0]), "modified directly")
255         self.failUnless(not current(calc, nodes[1]), "direct source modified")
256         self.failUnless(current(calc, nodes[2]))
257         self.failUnless(not current(calc, nodes[3]), "modified directly")
258         self.failUnless(current(calc, nodes[4]))
259         self.failUnless(current(calc, nodes[5]))
260         self.failUnless(not current(calc, nodes[6]), "modified directly")
261         self.failUnless(not current(calc, nodes[7]), "indirect source modified")
262         self.failUnless(not current(calc, nodes[8]), "modified directory")
263         self.failUnless(not current(calc, nodes[9]), "direct source modified")
264         self.failUnless(not current(calc, nodes[10]), "indirect source modified")
265
266     def test_modify_same_time(self):
267
268         nodes = create_nodes(self.files)
269
270         calc = SCons.Sig.Calculator(self.module, 0)
271
272         write(calc, nodes)
273
274         #simulate a modification of some files without changing the timestamp:
275         self.files[0].modify('blah blah blah blah', 333)
276         self.files[3].modify('blah blah blah blah', 333)
277         self.files[6].modify('blah blah blah blah', 333)
278         self.files[8].modify('blah blah blah blah', 333)
279
280         clear(nodes)
281
282         for node in nodes:
283             self.failUnless(current(calc, node),
284                             "node %s should be current" % node.path)
285
286
287 class MD5TestCase(unittest.TestCase, SigTestBase):
288     """Test MD5 signatures"""
289
290     module = SCons.Sig.MD5
291
292 class TimeStampTestCase(unittest.TestCase, SigTestBase):
293     """Test timestamp signatures"""
294
295     module = SCons.Sig.TimeStamp
296
297 class CalcTestCase(unittest.TestCase):
298
299     def runTest(self):
300         class MySigModule:
301             def collect(self, signatures):
302                 return reduce(lambda x, y: x + y, signatures)
303             def current(self, newsig, oldsig):
304                 return newsig == oldsig
305             def signature(self, node):
306                 return node.get_csig()
307
308         class MyNode:
309             def __init__(self, name, bsig, csig):
310                 self.name = name
311                 self.bsig = bsig
312                 self.csig = csig
313                 self.kids = []
314                 self.ignore = []
315                 self.builder = None
316                 self.use_signature = 1
317             def has_builder(self):
318                 return not self.builder is None
319             def children(self):
320                 return filter(lambda x, i=self.ignore: x not in i, self.kids)
321             def all_children(self):
322                 return self.kids
323             def exists(self):
324                 return 1
325             def cached_exists(self):
326                 return 1
327             def get_bsig(self):
328                 return self.bsig
329             def set_bsig(self, bsig):
330                 self.bsig = bsig
331             def get_csig(self):
332                 return self.csig
333             def set_csig(self, csig):
334                 self.csig = csig
335             def store_csig(self):
336                 pass
337             def store_timestamp(self):
338                 pass
339             def get_prevsiginfo(self):
340                 return 0, self.bsig, self.csig
341             def get_stored_implicit(self):
342                 return None
343             def get_timestamp(self):
344                 return 1
345             def builder_sig_adapter(self):
346                 class MyAdapter:
347                     def get_csig(self):
348                         return 333
349                     def get_timestamp(self):
350                         return 444
351                 return MyAdapter()
352
353         self.module = MySigModule()
354         self.nodeclass = MyNode
355         self.test_Calc___init__()
356         self.test_Calc_bsig()
357         self.test_Calc_current()
358
359     def test_Calc___init__(self):
360         self.calc = SCons.Sig.Calculator(self.module)
361         assert self.calc.module == self.module
362
363     def test_Calc_bsig(self):
364         n1 = self.nodeclass('n1', 11, 12)
365         n2 = self.nodeclass('n2', 22, 23)
366         n3 = self.nodeclass('n3', 33, 34)
367         n1.builder = 1
368         n1.kids = [n2, n3]
369
370         assert self.calc.bsig(n1) == 55
371
372         n1.ignore = [n2]
373
374         assert self.calc.bsig(n1) == 33
375
376     def test_Calc_bsig(self):
377         n = self.nodeclass('n', 11, 12)
378
379         assert self.calc.csig(n) == 12
380
381     def test_Calc_current(self):
382         class NN(self.nodeclass):
383             def current(self):
384                 return None
385
386         nn = NN('nn', 33, 34)
387         assert not self.calc.current(nn, 30)
388         assert self.calc.current(nn, 33)
389
390 class SConsignEntryTestCase(unittest.TestCase):
391
392     def runTest(self):
393         e = SCons.Sig.SConsignEntry()
394         assert e.timestamp == None
395         assert e.csig == None
396         assert e.bsig == None
397         assert e.implicit == None
398
399 class SConsignFileTestCase(unittest.TestCase):
400
401     def runTest(self):
402         class DummyModule:
403             def to_string(self, sig):
404                 return str(sig)
405
406             def from_string(self, sig):
407                 return int(sig)
408             
409         class DummyNode:
410             path = 'not_a_valid_path'
411
412         f = SCons.Sig.SConsignFile(DummyNode(), DummyModule())
413         f.set_bsig('foo', 1)
414         assert f.get('foo') == (None, 1, None)
415         f.set_csig('foo', 2)
416         assert f.get('foo') == (None, 1, 2)
417         f.set_timestamp('foo', 3)
418         assert f.get('foo') == (3, 1, 2)
419         f.set_implicit('foo', ['bar'])
420         assert f.get('foo') == (3, 1, 2)
421         assert f.get_implicit('foo') == ['bar']
422
423
424 def suite():
425     suite = unittest.TestSuite()
426     suite.addTest(MD5TestCase())
427     suite.addTest(TimeStampTestCase())
428     suite.addTest(CalcTestCase())
429     suite.addTest(SConsignEntryTestCase())
430     suite.addTest(SConsignFileTestCase())
431     return suite
432
433 if __name__ == "__main__":
434     runner = unittest.TextTestRunner()
435     result = runner.run(suite())
436     if not result.wasSuccessful():
437         sys.exit(1)
438