Fix use of timestamps with --implicit-cache. (Anthony Roach)
[scons.git] / src / engine / SCons / Scanner / ScannerTests.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 import UserDict
29 import SCons.Sig
30
31 import SCons.Scanner
32
33 class DummyEnvironment(UserDict.UserDict):
34     def __init__(self, dict=None, **kw):
35         UserDict.UserDict.__init__(self, dict)
36         self.data.update(kw)
37     def subst(self, strSubst):
38         if strSubst[0] == '$':
39             return self.data[strSubst[1:]]
40         return strSubst
41     def subst_list(self, strSubst):
42         if strSubst[0] == '$':
43             return [self.data[strSubst[1:]]]
44         return [[strSubst]]
45     def subst_path(self, path):
46         if type(path) != type([]):
47             path = [path]
48         return map(self.subst, path)
49     def get_calculator(self):
50         return SCons.Sig.default_calc
51
52 class FindPathDirsTestCase(unittest.TestCase):
53     def test_FindPathDirs(self):
54         """Test the FindPathDirs callable class"""
55
56         class FS:
57             def Rsearchall(self, nodes, must_exist=0, clazz=None, cwd=dir):
58                 return ['xxx'] + nodes
59
60         env = DummyEnvironment(LIBPATH = [ 'foo' ])
61
62         fpd = SCons.Scanner.FindPathDirs('LIBPATH', FS())
63         result = fpd(env, dir)
64         assert result == ('xxx', 'foo'), result
65
66 class ScannerTestCase(unittest.TestCase):
67
68     def func(self, filename, env, target, *args):
69         self.filename = filename
70         self.env = env
71         self.target = target
72
73         if len(args) > 0:
74             self.arg = args[0]
75
76         return self.deps
77
78     def test(self, scanner, env, filename, deps, *args):
79         self.deps = deps
80         path = scanner.path(env)
81         scanned = scanner(filename, env, path)
82         scanned_strs = map(lambda x: str(x), scanned)
83
84         self.failUnless(self.filename == filename, "the filename was passed incorrectly")
85         self.failUnless(self.env == env, "the environment was passed incorrectly")
86         self.failUnless(scanned_strs == deps, "the dependencies were returned incorrectly")
87         for d in scanned:
88             self.failUnless(type(d) != type(""), "got a string in the dependencies")
89
90         if len(args) > 0:
91             self.failUnless(self.arg == args[0], "the argument was passed incorrectly")
92         else:
93             self.failIf(hasattr(self, "arg"), "an argument was given when it shouldn't have been")
94
95     def test_positional(self):
96         """Test the Scanner.Base class using positional arguments"""
97         s = SCons.Scanner.Base(self.func, "Pos")
98         env = DummyEnvironment()
99         env.VARIABLE = "var1"
100         self.test(s, env, 'f1.cpp', ['f1.h', 'f1.hpp'])
101
102         env = DummyEnvironment()
103         env.VARIABLE = "i1"
104         self.test(s, env, 'i1.cpp', ['i1.h', 'i1.hpp'])
105
106     def test_keywords(self):
107         """Test the Scanner.Base class using keyword arguments"""
108         s = SCons.Scanner.Base(function = self.func, name = "Key")
109         env = DummyEnvironment()
110         env.VARIABLE = "var2"
111         self.test(s, env, 'f2.cpp', ['f2.h', 'f2.hpp'])
112
113         env = DummyEnvironment()
114         env.VARIABLE = "i2"
115         self.test(s, env, 'i2.cpp', ['i2.h', 'i2.hpp'])
116
117     def test_pos_opt(self):
118         """Test the Scanner.Base class using both position and optional arguments"""
119         arg = "this is the argument"
120         s = SCons.Scanner.Base(self.func, "PosArg", arg)
121         env = DummyEnvironment()
122         env.VARIABLE = "var3"
123         self.test(s, env, 'f3.cpp', ['f3.h', 'f3.hpp'], arg)
124
125         env = DummyEnvironment()
126         env.VARIABLE = "i3"
127         self.test(s, env, 'i3.cpp', ['i3.h', 'i3.hpp'], arg)
128
129     def test_key_opt(self):
130         """Test the Scanner.Base class using both keyword and optional arguments"""
131         arg = "this is another argument"
132         s = SCons.Scanner.Base(function = self.func, name = "KeyArg",
133                                argument = arg)
134         env = DummyEnvironment()
135         env.VARIABLE = "var4"
136         self.test(s, env, 'f4.cpp', ['f4.h', 'f4.hpp'], arg)
137
138         env = DummyEnvironment()
139         env.VARIABLE = "i4"
140         self.test(s, env, 'i4.cpp', ['i4.h', 'i4.hpp'], arg)
141
142     def test_hash(self):
143         """Test the Scanner.Base class __hash__() method"""
144         s = SCons.Scanner.Base(self.func, "Hash")
145         dict = {}
146         dict[s] = 777
147         self.failUnless(hash(dict.keys()[0]) == hash(repr(s)),
148                         "did not hash Scanner base class as expected")
149
150     def test_scan_check(self):
151         """Test the Scanner.Base class scan_check() method"""
152         def my_scan(filename, env, target, *args):
153             return []
154         def check(node, env, s=self):
155             s.checked[node] = 1
156             return 1
157         env = DummyEnvironment()
158         s = SCons.Scanner.Base(my_scan, "Check", scan_check = check)
159         self.checked = {}
160         path = s.path(env)
161         scanned = s('x', env, path)
162         self.failUnless(self.checked['x'] == 1,
163                         "did not call check function")
164
165     def test_recursive(self):
166         """Test the Scanner.Base class recursive flag"""
167         s = SCons.Scanner.Base(function = self.func)
168         self.failUnless(s.recursive == None,
169                         "incorrect default recursive value")
170         s = SCons.Scanner.Base(function = self.func, recursive = None)
171         self.failUnless(s.recursive == None,
172                         "did not set recursive flag to None")
173         s = SCons.Scanner.Base(function = self.func, recursive = 1)
174         self.failUnless(s.recursive == 1,
175                         "did not set recursive flag to 1")
176
177     def test_get_skeys(self):
178         """Test the Scanner.Base get_skeys() method"""
179         s = SCons.Scanner.Base(function = self.func)
180         sk = s.get_skeys()
181         self.failUnless(sk == [],
182                         "did not initialize to expected []")
183
184         s = SCons.Scanner.Base(function = self.func, skeys = ['.1', '.2'])
185         sk = s.get_skeys()
186         self.failUnless(sk == ['.1', '.2'],
187                         "sk was %s, not ['.1', '.2']")
188
189         s = SCons.Scanner.Base(function = self.func, skeys = '$LIST')
190         env = DummyEnvironment(LIST = ['.3', '.4'])
191         sk = s.get_skeys(env)
192         self.failUnless(sk == ['.3', '.4'],
193                         "sk was %s, not ['.3', '.4']")
194
195 class CurrentTestCase(unittest.TestCase):
196     def test_class(self):
197         """Test the Scanner.Current class"""
198         class MyNode:
199             def __init__(self):
200                 self.called_has_builder = None
201                 self.called_current = None
202                 self.func_called = None
203         class HasNoBuilder(MyNode):
204             def has_builder(self):
205                 self.called_has_builder = 1
206                 return None
207         class IsNotCurrent(MyNode):
208             def has_builder(self):
209                 self.called_has_builder = 1
210                 return 1
211             def current(self, sig):
212                 self.called_current = 1
213                 return None
214         class IsCurrent(MyNode):
215             def has_builder(self):
216                 self.called_has_builder = 1
217                 return 1
218             def current(self, sig):
219                 self.called_current = 1
220                 return 1
221         def func(node, env, path):
222             node.func_called = 1
223             return []
224         env = DummyEnvironment()
225         s = SCons.Scanner.Current(func)
226         path = s.path(env)
227         hnb = HasNoBuilder()
228         s(hnb, env, path)
229         self.failUnless(hnb.called_has_builder, "did not call has_builder()")
230         self.failUnless(not hnb.called_current, "did call current()")
231         self.failUnless(hnb.func_called, "did not call func()")
232         inc = IsNotCurrent()
233         s(inc, env, path)
234         self.failUnless(inc.called_has_builder, "did not call has_builder()")
235         self.failUnless(inc.called_current, "did not call current()")
236         self.failUnless(not inc.func_called, "did call func()")
237         ic = IsCurrent()
238         s(ic, env, path)
239         self.failUnless(ic.called_has_builder, "did not call has_builder()")
240         self.failUnless(ic.called_current, "did not call current()")
241         self.failUnless(ic.func_called, "did not call func()")
242
243 class ClassicTestCase(unittest.TestCase):
244     def test_find_include(self):
245         """Test the Scanner.Classic find_include() method"""
246         env = DummyEnvironment()
247         s = SCons.Scanner.Classic("t", ['.suf'], 'MYPATH', '^my_inc (\S+)')
248
249         def _find_file(filename, paths, factory):
250             return paths[0]+'/'+filename
251
252         save = SCons.Node.FS.find_file
253         SCons.Node.FS.find_file = _find_file
254
255         try:
256             n, i = s.find_include('aaa', 'foo', ('path',))
257             assert n == 'foo/aaa', n
258             assert i == 'aaa', i
259
260         finally:
261             SCons.Node.FS.find_file = save
262
263     def test_name(self):
264         """Test setting the Scanner.Classic name"""
265         s = SCons.Scanner.Classic("my_name", ['.s'], 'MYPATH', '^my_inc (\S+)')
266         assert s.name == "my_name", s.name
267
268     def test_scan(self):
269         """Test the Scanner.Classic scan() method"""
270         class MyNode:
271             def __init__(self, name):
272                 self.name = name
273                 self._rfile = self
274                 self.includes = None
275             def rfile(self):
276                 return self._rfile
277             def exists(self):
278                 return self._exists
279             def get_contents(self):
280                 return self._contents
281             def get_dir(self):
282                 return self._dir
283
284         class MyScanner(SCons.Scanner.Classic):
285             def find_include(self, include, source_dir, path):
286                 return include, include
287
288         env = DummyEnvironment()
289         s = MyScanner("t", ['.suf'], 'MYPATH', '^my_inc (\S+)')
290
291         # If the node doesn't exist, scanning turns up nothing.
292         n1 = MyNode("n1")
293         n1._exists = None
294         ret = s.scan(n1, env)
295         assert ret == [], ret
296
297         # Verify that it finds includes from the contents.
298         n = MyNode("n")
299         n._exists = 1
300         n._dir = MyNode("n._dir")
301         n._contents = 'my_inc abc\n'
302         ret = s.scan(n, env)
303         assert ret == ['abc'], ret
304
305         # Verify that it uses the cached include info.
306         n._contents = 'my_inc def\n'
307         ret = s.scan(n, env)
308         assert ret == ['abc'], ret
309
310         # Verify that if we wipe the cache, it uses the new contents.
311         n.includes = None
312         ret = s.scan(n, env)
313         assert ret == ['def'], ret
314
315         # Verify that it sorts what it finds.
316         n.includes = ['xyz', 'uvw']
317         ret = s.scan(n, env)
318         assert ret == ['uvw', 'xyz'], ret
319
320         # Verify that we use the rfile() node.
321         nr = MyNode("nr")
322         nr._exists = 1
323         nr._dir = MyNode("nr._dir")
324         nr.includes = ['jkl', 'mno']
325         n._rfile = nr
326         ret = s.scan(n, env)
327         assert ret == ['jkl', 'mno'], ret
328
329 class ClassicCPPTestCase(unittest.TestCase):
330     def test_find_include(self):
331         """Test the Scanner.ClassicCPP find_include() method"""
332         env = DummyEnvironment()
333         s = SCons.Scanner.ClassicCPP("Test", [], None, "")
334
335         def _find_file(filename, paths, factory):
336             return paths[0]+'/'+filename
337
338         save = SCons.Node.FS.find_file
339         SCons.Node.FS.find_file = _find_file
340
341         try:
342             n, i = s.find_include(('"', 'aaa'), 'foo', ('path',))
343             assert n == 'foo/aaa', n
344             assert i == 'aaa', i
345
346             n, i = s.find_include(('<', 'bbb'), 'foo', ('path',))
347             assert n == 'path/bbb', n
348             assert i == 'bbb', i
349
350         finally:
351             SCons.Node.FS.find_file = _find_file
352
353 def suite():
354     suite = unittest.TestSuite()
355     tclasses = [
356                  FindPathDirsTestCase,
357                  ScannerTestCase,
358                  CurrentTestCase,
359                  ClassicTestCase,
360                  ClassicCPPTestCase,
361                ]
362     for tclass in tclasses:
363         names = unittest.getTestCaseNames(tclass, 'test_')
364         suite.addTests(map(tclass, names))
365     return suite
366
367 if __name__ == "__main__":
368     runner = unittest.TextTestRunner()
369     result = runner.run(suite())
370     if not result.wasSuccessful():
371         sys.exit(1)