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