518e0ed40763f3e0dae587513c992e10aae1d910
[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
30 import SCons.Scanner
31
32 class DummyFS:
33     def File(self, name):
34         return DummyNode(name)
35
36 class DummyEnvironment(UserDict.UserDict):
37     def __init__(self, dict=None, **kw):
38         UserDict.UserDict.__init__(self, dict)
39         self.data.update(kw)
40         self.fs = DummyFS()
41     def subst(self, strSubst, target=None, source=None, conv=None):
42         if strSubst[0] == '$':
43             return self.data[strSubst[1:]]
44         return strSubst
45     def subst_list(self, strSubst, target=None, source=None, conv=None):
46         if strSubst[0] == '$':
47             return [self.data[strSubst[1:]]]
48         return [[strSubst]]
49     def subst_path(self, path, target=None, source=None, conv=None):
50         if type(path) != type([]):
51             path = [path]
52         return map(self.subst, path)
53     def get_factory(self, factory):
54         return factory or self.fs.File
55
56 class DummyNode:
57     def __init__(self, name, search_result=()):
58         self.name = name
59         self.search_result = tuple(search_result)
60     def rexists(self):
61         return 1
62     def __str__(self):
63         return self.name
64     def Rfindalldirs(self, pathlist):
65         return self.search_result + pathlist
66
67 class FindPathDirsTestCase(unittest.TestCase):
68     def test_FindPathDirs(self):
69         """Test the FindPathDirs callable class"""
70
71         env = DummyEnvironment(LIBPATH = [ 'foo' ])
72         env.fs = DummyFS()
73         env.fs._cwd = DummyNode('cwd')
74
75         dir = DummyNode('dir', ['xxx'])
76         fpd = SCons.Scanner.FindPathDirs('LIBPATH')
77         result = fpd(env)
78         assert str(result) == "('foo',)", result
79         result = fpd(env, dir)
80         assert str(result) == "('xxx', 'foo')", result
81
82 class ScannerTestCase(unittest.TestCase):
83
84     def test_creation(self):
85         """Test creation of Scanner objects"""
86         def func(self):
87             pass
88         s = SCons.Scanner.Base(func)
89         assert isinstance(s, SCons.Scanner.Base), s
90         s = SCons.Scanner.Base({})
91         assert isinstance(s, SCons.Scanner.Base), s
92
93         s = SCons.Scanner.Base(func, name='fooscan')
94         assert str(s) == 'fooscan', str(s)
95         s = SCons.Scanner.Base({}, name='barscan')
96         assert str(s) == 'barscan', str(s)
97
98         s = SCons.Scanner.Base(func, name='fooscan', argument=9)
99         assert str(s) == 'fooscan', str(s)
100         assert s.argument == 9, s.argument
101         s = SCons.Scanner.Base({}, name='fooscan', argument=888)
102         assert str(s) == 'fooscan', str(s)
103         assert s.argument == 888, s.argument
104
105         
106 class BaseTestCase(unittest.TestCase):
107
108     class skey_node:
109         def __init__(self, key):
110             self.key = key
111         def scanner_key(self):
112             return self.key
113         def rexists(self):
114             return 1
115
116     def func(self, filename, env, target, *args):
117         self.filename = filename
118         self.env = env
119         self.target = target
120
121         if len(args) > 0:
122             self.arg = args[0]
123
124         return self.deps
125
126     def test(self, scanner, env, filename, deps, *args):
127         self.deps = deps
128         path = scanner.path(env)
129         scanned = scanner(filename, env, path)
130         scanned_strs = map(lambda x: str(x), scanned)
131
132         self.failUnless(self.filename == filename, "the filename was passed incorrectly")
133         self.failUnless(self.env == env, "the environment was passed incorrectly")
134         self.failUnless(scanned_strs == deps, "the dependencies were returned incorrectly")
135         for d in scanned:
136             self.failUnless(type(d) != type(""), "got a string in the dependencies")
137
138         if len(args) > 0:
139             self.failUnless(self.arg == args[0], "the argument was passed incorrectly")
140         else:
141             self.failIf(hasattr(self, "arg"), "an argument was given when it shouldn't have been")
142
143     def test___call__dict(self):
144         """Test calling Scanner.Base objects with a dictionary"""
145         called = []
146         def s1func(node, env, path, called=called):
147             called.append('s1func')
148             called.append(node)
149             return []
150         def s2func(node, env, path, called=called):
151             called.append('s2func')
152             called.append(node)
153             return []
154         s1 = SCons.Scanner.Base(s1func)
155         s2 = SCons.Scanner.Base(s2func)
156         selector = SCons.Scanner.Base({'.x' : s1, '.y' : s2})
157         nx = self.skey_node('.x')
158         env = DummyEnvironment()
159         selector(nx, env, [])
160         assert called == ['s1func', nx], called
161         del called[:]
162         ny = self.skey_node('.y')
163         selector(ny, env, [])
164         assert called == ['s2func', ny], called
165
166     def test_path(self):
167         """Test the Scanner.Base path() method"""
168         def pf(env, cwd, target, source, argument=None):
169             return "pf: %s %s %s %s %s" % \
170                         (env.VARIABLE, cwd, target[0], source[0], argument)
171
172         env = DummyEnvironment()
173         env.VARIABLE = 'v1'
174         target = DummyNode('target')
175         source = DummyNode('source')
176
177         s = SCons.Scanner.Base(self.func, path_function=pf)
178         p = s.path(env, 'here', [target], [source])
179         assert p == "pf: v1 here target source None", p
180
181         s = SCons.Scanner.Base(self.func, path_function=pf, argument="xyz")
182         p = s.path(env, 'here', [target], [source])
183         assert p == "pf: v1 here target source xyz", p
184
185     def test_positional(self):
186         """Test the Scanner.Base class using positional arguments"""
187         s = SCons.Scanner.Base(self.func, "Pos")
188         env = DummyEnvironment()
189         env.VARIABLE = "var1"
190         self.test(s, env, DummyNode('f1.cpp'), ['f1.h', 'f1.hpp'])
191
192         env = DummyEnvironment()
193         env.VARIABLE = "i1"
194         self.test(s, env, DummyNode('i1.cpp'), ['i1.h', 'i1.hpp'])
195
196     def test_keywords(self):
197         """Test the Scanner.Base class using keyword arguments"""
198         s = SCons.Scanner.Base(function = self.func, name = "Key")
199         env = DummyEnvironment()
200         env.VARIABLE = "var2"
201         self.test(s, env, DummyNode('f2.cpp'), ['f2.h', 'f2.hpp'])
202
203         env = DummyEnvironment()
204         env.VARIABLE = "i2"
205
206         self.test(s, env, DummyNode('i2.cpp'), ['i2.h', 'i2.hpp'])
207
208     def test_pos_opt(self):
209         """Test the Scanner.Base class using both position and optional arguments"""
210         arg = "this is the argument"
211         s = SCons.Scanner.Base(self.func, "PosArg", arg)
212         env = DummyEnvironment()
213         env.VARIABLE = "var3"
214         self.test(s, env, DummyNode('f3.cpp'), ['f3.h', 'f3.hpp'], arg)
215
216         env = DummyEnvironment()
217         env.VARIABLE = "i3"
218         self.test(s, env, DummyNode('i3.cpp'), ['i3.h', 'i3.hpp'], arg)
219
220     def test_key_opt(self):
221         """Test the Scanner.Base class using both keyword and optional arguments"""
222         arg = "this is another argument"
223         s = SCons.Scanner.Base(function = self.func, name = "KeyArg",
224                                argument = arg)
225         env = DummyEnvironment()
226         env.VARIABLE = "var4"
227         self.test(s, env, DummyNode('f4.cpp'), ['f4.h', 'f4.hpp'], arg)
228
229         env = DummyEnvironment()
230         env.VARIABLE = "i4"
231         self.test(s, env, DummyNode('i4.cpp'), ['i4.h', 'i4.hpp'], arg)
232
233     def test___cmp__(self):
234         """Test the Scanner.Base class __cmp__() method"""
235         s = SCons.Scanner.Base(self.func, "Cmp")
236         assert cmp(s, None)
237
238     def test_hash(self):
239         """Test the Scanner.Base class __hash__() method"""
240         s = SCons.Scanner.Base(self.func, "Hash")
241         dict = {}
242         dict[s] = 777
243         i = hash(id(s))
244         h = hash(dict.keys()[0])
245         self.failUnless(h == i,
246                         "hash Scanner base class expected %s, got %s" % (i, h))
247
248     def test_scan_check(self):
249         """Test the Scanner.Base class scan_check() method"""
250         def my_scan(filename, env, target, *args):
251             return []
252         def check(node, env, s=self):
253             s.checked[str(node)] = 1
254             return 1
255         env = DummyEnvironment()
256         s = SCons.Scanner.Base(my_scan, "Check", scan_check = check)
257         self.checked = {}
258         path = s.path(env)
259         scanned = s(DummyNode('x'), env, path)
260         self.failUnless(self.checked['x'] == 1,
261                         "did not call check function")
262
263     def test_recursive(self):
264         """Test the Scanner.Base class recursive flag"""
265         nodes = [1, 2, 3, 4]
266
267         s = SCons.Scanner.Base(function = self.func)
268         n = s.recurse_nodes(nodes)
269         self.failUnless(n == [],
270                         "default behavior returned nodes: %s" % n)
271
272         s = SCons.Scanner.Base(function = self.func, recursive = None)
273         n = s.recurse_nodes(nodes)
274         self.failUnless(n == [],
275                         "recursive = None returned nodes: %s" % n)
276
277         s = SCons.Scanner.Base(function = self.func, recursive = 1)
278         n = s.recurse_nodes(nodes)
279         self.failUnless(n == n,
280                         "recursive = 1 didn't return all nodes: %s" % n)
281
282         def odd_only(nodes):
283             return filter(lambda n: n % 2, nodes)
284         s = SCons.Scanner.Base(function = self.func, recursive = odd_only)
285         n = s.recurse_nodes(nodes)
286         self.failUnless(n == [1, 3],
287                         "recursive = 1 didn't return all nodes: %s" % n)
288
289     def test_get_skeys(self):
290         """Test the Scanner.Base get_skeys() method"""
291         s = SCons.Scanner.Base(function = self.func)
292         sk = s.get_skeys()
293         self.failUnless(sk == [],
294                         "did not initialize to expected []")
295
296         s = SCons.Scanner.Base(function = self.func, skeys = ['.1', '.2'])
297         sk = s.get_skeys()
298         self.failUnless(sk == ['.1', '.2'],
299                         "sk was %s, not ['.1', '.2']")
300
301         s = SCons.Scanner.Base(function = self.func, skeys = '$LIST')
302         env = DummyEnvironment(LIST = ['.3', '.4'])
303         sk = s.get_skeys(env)
304         self.failUnless(sk == ['.3', '.4'],
305                         "sk was %s, not ['.3', '.4']")
306
307     def test_select(self):
308         """Test the Scanner.Base select() method"""
309         scanner = SCons.Scanner.Base(function = self.func)
310         s = scanner.select('.x')
311         assert s is scanner, s
312
313         selector = SCons.Scanner.Base({'.x' : 1, '.y' : 2})
314         s = selector.select(self.skey_node('.x'))
315         assert s == 1, s
316         s = selector.select(self.skey_node('.y'))
317         assert s == 2, s
318         s = selector.select(self.skey_node('.z'))
319         assert s is None, s
320
321     def test_add_scanner(self):
322         """Test the Scanner.Base add_scanner() method"""
323         selector = SCons.Scanner.Base({'.x' : 1, '.y' : 2})
324         s = selector.select(self.skey_node('.z'))
325         assert s is None, s
326         selector.add_scanner('.z', 3)
327         s = selector.select(self.skey_node('.z'))
328         assert s == 3, s
329
330     def test___str__(self):
331         """Test the Scanner.Base __str__() method"""
332         scanner = SCons.Scanner.Base(function = self.func)
333         s = str(scanner)
334         assert s == 'NONE', s
335         scanner = SCons.Scanner.Base(function = self.func, name = 'xyzzy')
336         s = str(scanner)
337         assert s == 'xyzzy', s
338
339 class SelectorTestCase(unittest.TestCase):
340     class skey_node:
341         def __init__(self, key):
342             self.key = key
343         def scanner_key(self):
344             return self.key
345         def rexists(self):
346             return 1
347
348     def test___init__(self):
349         """Test creation of Scanner.Selector object"""
350         s = SCons.Scanner.Selector({})
351         assert isinstance(s, SCons.Scanner.Selector), s
352         assert s.dict == {}, s.dict
353
354     def test___call__(self):
355         """Test calling Scanner.Selector objects"""
356         called = []
357         def s1func(node, env, path, called=called):
358             called.append('s1func')
359             called.append(node)
360             return []
361         def s2func(node, env, path, called=called):
362             called.append('s2func')
363             called.append(node)
364             return []
365         s1 = SCons.Scanner.Base(s1func)
366         s2 = SCons.Scanner.Base(s2func)
367         selector = SCons.Scanner.Selector({'.x' : s1, '.y' : s2})
368         nx = self.skey_node('.x')
369         env = DummyEnvironment()
370         selector(nx, env, [])
371         assert called == ['s1func', nx], called
372         del called[:]
373         ny = self.skey_node('.y')
374         selector(ny, env, [])
375         assert called == ['s2func', ny], called
376
377     def test_select(self):
378         """Test the Scanner.Selector select() method"""
379         selector = SCons.Scanner.Selector({'.x' : 1, '.y' : 2})
380         s = selector.select(self.skey_node('.x'))
381         assert s == 1, s
382         s = selector.select(self.skey_node('.y'))
383         assert s == 2, s
384         s = selector.select(self.skey_node('.z'))
385         assert s is None, s
386
387     def test_add_scanner(self):
388         """Test the Scanner.Selector add_scanner() method"""
389         selector = SCons.Scanner.Selector({'.x' : 1, '.y' : 2})
390         s = selector.select(self.skey_node('.z'))
391         assert s is None, s
392         selector.add_scanner('.z', 3)
393         s = selector.select(self.skey_node('.z'))
394         assert s == 3, s
395
396 class CurrentTestCase(unittest.TestCase):
397     def test_class(self):
398         """Test the Scanner.Current class"""
399         class MyNode:
400             def __init__(self):
401                 self.called_has_builder = None
402                 self.called_is_up_to_date = None
403                 self.func_called = None
404             def rexists(self):
405                 return 1
406         class HasNoBuilder(MyNode):
407             def has_builder(self):
408                 self.called_has_builder = 1
409                 return None
410         class IsNotCurrent(MyNode):
411             def has_builder(self):
412                 self.called_has_builder = 1
413                 return 1
414             def is_up_to_date(self):
415                 self.called_is_up_to_date = 1
416                 return None
417         class IsCurrent(MyNode):
418             def has_builder(self):
419                 self.called_has_builder = 1
420                 return 1
421             def is_up_to_date(self):
422                 self.called_is_up_to_date = 1
423                 return 1
424         def func(node, env, path):
425             node.func_called = 1
426             return []
427         env = DummyEnvironment()
428         s = SCons.Scanner.Current(func)
429         path = s.path(env)
430         hnb = HasNoBuilder()
431         s(hnb, env, path)
432         self.failUnless(hnb.called_has_builder, "did not call has_builder()")
433         self.failUnless(not hnb.called_is_up_to_date, "did call is_up_to_date()")
434         self.failUnless(hnb.func_called, "did not call func()")
435         inc = IsNotCurrent()
436         s(inc, env, path)
437         self.failUnless(inc.called_has_builder, "did not call has_builder()")
438         self.failUnless(inc.called_is_up_to_date, "did not call is_up_to_date()")
439         self.failUnless(not inc.func_called, "did call func()")
440         ic = IsCurrent()
441         s(ic, env, path)
442         self.failUnless(ic.called_has_builder, "did not call has_builder()")
443         self.failUnless(ic.called_is_up_to_date, "did not call is_up_to_date()")
444         self.failUnless(ic.func_called, "did not call func()")
445
446 class ClassicTestCase(unittest.TestCase):
447     def test_find_include(self):
448         """Test the Scanner.Classic find_include() method"""
449         env = DummyEnvironment()
450         s = SCons.Scanner.Classic("t", ['.suf'], 'MYPATH', '^my_inc (\S+)')
451
452         def _find_file(filename, paths):
453             return paths[0]+'/'+filename
454
455         save = SCons.Node.FS.find_file
456         SCons.Node.FS.find_file = _find_file
457
458         try:
459             n, i = s.find_include('aaa', 'foo', ('path',))
460             assert n == 'foo/aaa', n
461             assert i == 'aaa', i
462
463         finally:
464             SCons.Node.FS.find_file = save
465
466     def test_name(self):
467         """Test setting the Scanner.Classic name"""
468         s = SCons.Scanner.Classic("my_name", ['.s'], 'MYPATH', '^my_inc (\S+)')
469         assert s.name == "my_name", s.name
470
471     def test_scan(self):
472         """Test the Scanner.Classic scan() method"""
473         class MyNode:
474             def __init__(self, name):
475                 self.name = name
476                 self._rfile = self
477                 self.includes = None
478             def rfile(self):
479                 return self._rfile
480             def exists(self):
481                 return self._exists
482             def get_contents(self):
483                 return self._contents
484             def get_text_contents(self):
485                 return self._contents
486             def get_dir(self):
487                 return self._dir
488
489         class MyScanner(SCons.Scanner.Classic):
490             def find_include(self, include, source_dir, path):
491                 return include, include
492
493         env = DummyEnvironment()
494         s = MyScanner("t", ['.suf'], 'MYPATH', '^my_inc (\S+)')
495
496         # This set of tests is intended to test the scanning operation
497         # of the Classic scanner.
498
499         # Note that caching has been added for not just the includes
500         # but the entire scan call.  The caching is based on the
501         # arguments, so we will fiddle with the path parameter to
502         # defeat this caching for the purposes of these tests.
503
504         # If the node doesn't exist, scanning turns up nothing.
505         n1 = MyNode("n1")
506         n1._exists = None
507         ret = s.function(n1, env)
508         assert ret == [], ret
509
510         # Verify that it finds includes from the contents.
511         n = MyNode("n")
512         n._exists = 1
513         n._dir = MyNode("n._dir")
514         n._contents = 'my_inc abc\n'
515         ret = s.function(n, env, ('foo',))
516         assert ret == ['abc'], ret
517
518         # Verify that it uses the cached include info.
519         n._contents = 'my_inc def\n'
520         ret = s.function(n, env, ('foo2',))
521         assert ret == ['abc'], ret
522
523         # Verify that if we wipe the cache, it uses the new contents.
524         n.includes = None
525         ret = s.function(n, env, ('foo3',))
526         assert ret == ['def'], ret
527
528         # We no longer cache overall scan results, which would be returned
529         # if individual results are de-cached.  If we ever restore that
530         # functionality, this test goes back here.
531         #ret = s.function(n, env, ('foo2',))
532         #assert ret == ['abc'], 'caching inactive; got: %s'%ret
533
534         # Verify that it sorts what it finds.
535         n.includes = ['xyz', 'uvw']
536         ret = s.function(n, env, ('foo4',))
537         assert ret == ['uvw', 'xyz'], ret
538
539         # Verify that we use the rfile() node.
540         nr = MyNode("nr")
541         nr._exists = 1
542         nr._dir = MyNode("nr._dir")
543         nr.includes = ['jkl', 'mno']
544         n._rfile = nr
545         ret = s.function(n, env, ('foo5',))
546         assert ret == ['jkl', 'mno'], ret
547
548         
549
550 class ClassicCPPTestCase(unittest.TestCase):
551     def test_find_include(self):
552         """Test the Scanner.ClassicCPP find_include() method"""
553         env = DummyEnvironment()
554         s = SCons.Scanner.ClassicCPP("Test", [], None, "")
555
556         def _find_file(filename, paths):
557             return paths[0]+'/'+filename
558
559         save = SCons.Node.FS.find_file
560         SCons.Node.FS.find_file = _find_file
561
562         try:
563             n, i = s.find_include(('"', 'aaa'), 'foo', ('path',))
564             assert n == 'foo/aaa', n
565             assert i == 'aaa', i
566
567             n, i = s.find_include(('<', 'bbb'), 'foo', ('path',))
568             assert n == 'path/bbb', n
569             assert i == 'bbb', i
570
571             # TODO(1.5):  remove when 2.2 is minimal; replace ccc
572             # variable in find_include() call below with in-line u'ccc'.
573             try:
574                 ccc = eval("u'ccc'")
575             except SyntaxError:
576                 ccc = 'ccc'
577
578             n, i = s.find_include(('<', ccc), 'foo', ('path',))
579             assert n == 'path/ccc', n
580             assert i == 'ccc', i
581
582         finally:
583             SCons.Node.FS.find_file = save
584
585 def suite():
586     suite = unittest.TestSuite()
587     tclasses = [
588                  FindPathDirsTestCase,
589                  ScannerTestCase,
590                  BaseTestCase,
591                  SelectorTestCase,
592                  CurrentTestCase,
593                  ClassicTestCase,
594                  ClassicCPPTestCase,
595                ]
596     for tclass in tclasses:
597         names = unittest.getTestCaseNames(tclass, 'test_')
598         suite.addTests(map(tclass, names))
599     return suite
600
601 if __name__ == "__main__":
602     runner = unittest.TextTestRunner()
603     result = runner.run(suite())
604     if not result.wasSuccessful():
605         sys.exit(1)
606
607 # Local Variables:
608 # tab-width:4
609 # indent-tabs-mode:nil
610 # End:
611 # vim: set expandtab tabstop=4 shiftwidth=4: