Followon to PR #4348: more bool fixes
[scons.git] / SCons / Scanner / ScannerTests.py
bloba777ba5b3b5eb3f4095e038a5dc797fc6645e797
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 import collections
25 import unittest
27 import TestUnit
29 import SCons.compat
30 import SCons.Scanner
31 from SCons.Scanner import ScannerBase, Selector, Classic, ClassicCPP, Current, FindPathDirs
33 class DummyFS:
34 def File(self, name):
35 return DummyNode(name)
37 class DummyEnvironment(collections.UserDict):
38 def __init__(self, mapping=None, **kwargs) -> None:
39 super().__init__(mapping)
40 self.data.update(kwargs)
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 not isinstance(path, list):
52 path = [path]
53 return list(map(self.subst, path))
54 def get_factory(self, factory):
55 return factory or self.fs.File
57 class DummyNode:
58 def __init__(self, name, search_result=()) -> None:
59 self.name = name
60 self.search_result = tuple(search_result)
62 def rexists(self) -> bool:
63 return True
65 def __str__(self) -> str:
66 return self.name
68 def Rfindalldirs(self, pathlist):
69 return self.search_result + pathlist
70 def __repr__(self) -> str:
71 return self.name
72 def __eq__(self, other):
73 return self.name == other.name
75 def __repr__(self) -> str:
76 return self.name
78 def __eq__(self, other):
79 return self.name == other.name
81 class FindPathDirsTestCase(unittest.TestCase):
82 def test_FindPathDirs(self) -> None:
83 """Test the FindPathDirs callable class"""
85 env = DummyEnvironment(LIBPATH = [ 'foo' ])
86 env.fs = DummyFS()
87 env.fs._cwd = DummyNode('cwd')
89 dir = DummyNode('dir', ['xxx'])
90 fpd = FindPathDirs('LIBPATH')
91 result = fpd(env)
92 assert str(result) == "('foo',)", result
93 result = fpd(env, dir)
94 assert str(result) == "('xxx', 'foo')", result
96 class ScannerTestCase(unittest.TestCase):
98 def test_creation(self) -> None:
99 """Test creation of Scanner objects"""
100 def func(self) -> None:
101 pass
102 s = ScannerBase(func)
103 assert isinstance(s, ScannerBase), s
104 s = ScannerBase({})
105 assert isinstance(s, ScannerBase), s
107 s = ScannerBase(func, name='fooscan')
108 assert str(s) == 'fooscan', str(s)
109 s = ScannerBase({}, name='barscan')
110 assert str(s) == 'barscan', str(s)
112 s = ScannerBase(func, name='fooscan', argument=9)
113 assert str(s) == 'fooscan', str(s)
114 assert s.argument == 9, s.argument
115 s = ScannerBase({}, name='fooscan', argument=888)
116 assert str(s) == 'fooscan', str(s)
117 assert s.argument == 888, s.argument
120 class ScannerBaseTestCase(unittest.TestCase):
122 class skey_node:
123 def __init__(self, key) -> None:
124 self.key = key
125 def scanner_key(self):
126 return self.key
127 def rexists(self) -> bool:
128 return True
130 def func(self, filename, env, target, *args):
131 self.filename = filename
132 self.env = env
133 self.target = target
135 if len(args) > 0:
136 self.arg = args[0]
138 return self.deps
140 def test(self, scanner, env, filename, deps, *args) -> None:
141 self.deps = deps
142 path = scanner.path(env)
143 scanned = scanner(filename, env, path)
144 scanned_strs = [str(x) for x in scanned]
146 self.assertTrue(self.filename == filename, "the filename was passed incorrectly")
147 self.assertTrue(self.env == env, "the environment was passed incorrectly")
148 self.assertTrue(scanned_strs == deps, "the dependencies were returned incorrectly")
149 for d in scanned:
150 self.assertTrue(not isinstance(d, str), "got a string in the dependencies")
152 if len(args) > 0:
153 self.assertTrue(self.arg == args[0], "the argument was passed incorrectly")
154 else:
155 self.assertFalse(hasattr(self, "arg"), "an argument was given when it shouldn't have been")
157 def test___call__dict(self) -> None:
158 """Test calling ScannerBase objects with a dictionary"""
159 called = []
160 def s1func(node, env, path, called=called):
161 called.append('s1func')
162 called.append(node)
163 return []
164 def s2func(node, env, path, called=called):
165 called.append('s2func')
166 called.append(node)
167 return []
168 s1 = ScannerBase(s1func)
169 s2 = ScannerBase(s2func)
170 selector = ScannerBase({'.x' : s1, '.y' : s2})
171 nx = self.skey_node('.x')
172 env = DummyEnvironment()
173 selector(nx, env, [])
174 assert called == ['s1func', nx], called
175 del called[:]
176 ny = self.skey_node('.y')
177 selector(ny, env, [])
178 assert called == ['s2func', ny], called
180 def test_path(self) -> None:
181 """Test the ScannerBase path() method"""
182 def pf(env, cwd, target, source, argument=None) -> str:
183 return "pf: %s %s %s %s %s" % \
184 (env.VARIABLE, cwd, target[0], source[0], argument)
186 env = DummyEnvironment()
187 env.VARIABLE = 'v1'
188 target = DummyNode('target')
189 source = DummyNode('source')
191 s = ScannerBase(self.func, path_function=pf)
192 p = s.path(env, 'here', [target], [source])
193 assert p == "pf: v1 here target source None", p
195 s = ScannerBase(self.func, path_function=pf, argument="xyz")
196 p = s.path(env, 'here', [target], [source])
197 assert p == "pf: v1 here target source xyz", p
199 def test_positional(self) -> None:
200 """Test the ScannerBase class using positional arguments"""
201 s = ScannerBase(self.func, "Pos")
202 env = DummyEnvironment()
203 env.VARIABLE = "var1"
204 self.test(s, env, DummyNode('f1.cpp'), ['f1.h', 'f1.hpp'])
206 env = DummyEnvironment()
207 env.VARIABLE = "i1"
208 self.test(s, env, DummyNode('i1.cpp'), ['i1.h', 'i1.hpp'])
210 def test_keywords(self) -> None:
211 """Test the ScannerBase class using keyword arguments"""
212 s = ScannerBase(function = self.func, name = "Key")
213 env = DummyEnvironment()
214 env.VARIABLE = "var2"
215 self.test(s, env, DummyNode('f2.cpp'), ['f2.h', 'f2.hpp'])
217 env = DummyEnvironment()
218 env.VARIABLE = "i2"
220 self.test(s, env, DummyNode('i2.cpp'), ['i2.h', 'i2.hpp'])
222 def test_pos_opt(self) -> None:
223 """Test the ScannerBase class using both position and optional arguments"""
224 arg = "this is the argument"
225 s = ScannerBase(self.func, "PosArg", arg)
226 env = DummyEnvironment()
227 env.VARIABLE = "var3"
228 self.test(s, env, DummyNode('f3.cpp'), ['f3.h', 'f3.hpp'], arg)
230 env = DummyEnvironment()
231 env.VARIABLE = "i3"
232 self.test(s, env, DummyNode('i3.cpp'), ['i3.h', 'i3.hpp'], arg)
234 def test_key_opt(self) -> None:
235 """Test the ScannerBase class using both keyword and optional arguments"""
236 arg = "this is another argument"
237 s = ScannerBase(function = self.func, name = "KeyArg", argument = arg)
238 env = DummyEnvironment()
239 env.VARIABLE = "var4"
240 self.test(s, env, DummyNode('f4.cpp'), ['f4.h', 'f4.hpp'], arg)
242 env = DummyEnvironment()
243 env.VARIABLE = "i4"
244 self.test(s, env, DummyNode('i4.cpp'), ['i4.h', 'i4.hpp'], arg)
246 def test___cmp__(self) -> None:
247 """Test the ScannerBase class __cmp__() method"""
248 s = ScannerBase(self.func, "Cmp")
249 assert s is not None
251 def test_hash(self) -> None:
252 """Test the ScannerBase class __hash__() method"""
253 s = ScannerBase(self.func, "Hash")
254 mapping = {}
255 mapping[s] = 777
256 i = hash(id(s))
257 h = hash(list(mapping)[0])
258 self.assertTrue(h == i,
259 "hash Scanner base class expected %s, got %s" % (i, h))
261 def test_scan_check(self) -> None:
262 """Test the ScannerBase class scan_check() method"""
263 def my_scan(filename, env, target, *args):
264 return []
265 def check(node, env, s=self) -> int:
266 s.checked[str(node)] = 1
267 return 1
268 env = DummyEnvironment()
269 s = ScannerBase(my_scan, "Check", scan_check = check)
270 self.checked = {}
271 path = s.path(env)
272 scanned = s(DummyNode('x'), env, path)
273 self.assertTrue(self.checked['x'] == 1,
274 "did not call check function")
276 def test_recursive(self) -> None:
277 """Test the ScannerBase class recursive flag"""
278 nodes = [1, 2, 3, 4]
280 s = ScannerBase(function = self.func)
281 n = s.recurse_nodes(nodes)
282 self.assertTrue(n == [], "default behavior returned nodes: %s" % n)
284 s = ScannerBase(function = self.func, recursive = None)
285 n = s.recurse_nodes(nodes)
286 self.assertTrue(n == [], "recursive = None returned nodes: %s" % n)
288 s = ScannerBase(function = self.func, recursive = 1)
289 n = s.recurse_nodes(nodes)
290 self.assertTrue(n == n, "recursive = 1 didn't return all nodes: %s" % n)
292 def odd_only(nodes):
293 return [n for n in nodes if n % 2]
294 s = ScannerBase(function = self.func, recursive = odd_only)
295 n = s.recurse_nodes(nodes)
296 self.assertTrue(n == [1, 3], "recursive = 1 didn't return all nodes: %s" % n)
298 def test_get_skeys(self) -> None:
299 """Test the ScannerBase get_skeys() method"""
300 s = ScannerBase(function = self.func)
301 sk = s.get_skeys()
302 self.assertTrue(sk == [], "did not initialize to expected []")
304 s = ScannerBase(function = self.func, skeys = ['.1', '.2'])
305 sk = s.get_skeys()
306 self.assertTrue(sk == ['.1', '.2'], "sk was %s, not ['.1', '.2']")
308 s = ScannerBase(function = self.func, skeys = '$LIST')
309 env = DummyEnvironment(LIST = ['.3', '.4'])
310 sk = s.get_skeys(env)
311 self.assertTrue(sk == ['.3', '.4'], "sk was %s, not ['.3', '.4']")
313 def test_select(self) -> None:
314 """Test the ScannerBase select() method"""
315 scanner = ScannerBase(function = self.func)
316 s = scanner.select('.x')
317 assert s is scanner, s
319 selector = ScannerBase({'.x' : 1, '.y' : 2})
320 s = selector.select(self.skey_node('.x'))
321 assert s == 1, s
322 s = selector.select(self.skey_node('.y'))
323 assert s == 2, s
324 s = selector.select(self.skey_node('.z'))
325 assert s is None, s
327 def test_add_scanner(self) -> None:
328 """Test the ScannerBase add_scanner() method"""
329 selector = ScannerBase({'.x' : 1, '.y' : 2})
330 s = selector.select(self.skey_node('.z'))
331 assert s is None, s
332 selector.add_scanner('.z', 3)
333 s = selector.select(self.skey_node('.z'))
334 assert s == 3, s
336 def test___str__(self) -> None:
337 """Test the ScannerBase __str__() method"""
338 scanner = ScannerBase(function = self.func)
339 s = str(scanner)
340 assert s == 'NONE', s
341 scanner = ScannerBase(function = self.func, name = 'xyzzy')
342 s = str(scanner)
343 assert s == 'xyzzy', s
345 class SelectorTestCase(unittest.TestCase):
347 class skey_node:
348 def __init__(self, key) -> None:
349 self.key = key
350 def scanner_key(self):
351 return self.key
352 def rexists(self) -> bool:
353 return True
355 def test___init__(self) -> None:
356 """Test creation of Scanner.Selector object"""
357 s = Selector({})
358 assert isinstance(s, Selector), s
359 assert s.mapping == {}, s.mapping
361 def test___call__(self) -> None:
362 """Test calling Scanner.Selector objects"""
363 called = []
364 def s1func(node, env, path, called=called):
365 called.append('s1func')
366 called.append(node)
367 return []
368 def s2func(node, env, path, called=called):
369 called.append('s2func')
370 called.append(node)
371 return []
372 s1 = ScannerBase(s1func)
373 s2 = ScannerBase(s2func)
374 selector = Selector({'.x' : s1, '.y' : s2})
375 nx = self.skey_node('.x')
376 env = DummyEnvironment()
377 selector(nx, env, [])
378 assert called == ['s1func', nx], called
379 del called[:]
380 ny = self.skey_node('.y')
381 selector(ny, env, [])
382 assert called == ['s2func', ny], called
384 def test_select(self) -> None:
385 """Test the Scanner.Selector select() method"""
386 selector = Selector({'.x' : 1, '.y' : 2})
387 s = selector.select(self.skey_node('.x'))
388 assert s == 1, s
389 s = selector.select(self.skey_node('.y'))
390 assert s == 2, s
391 s = selector.select(self.skey_node('.z'))
392 assert s is None, s
394 def test_add_scanner(self) -> None:
395 """Test the Scanner.Selector add_scanner() method"""
396 selector = Selector({'.x' : 1, '.y' : 2})
397 s = selector.select(self.skey_node('.z'))
398 assert s is None, s
399 selector.add_scanner('.z', 3)
400 s = selector.select(self.skey_node('.z'))
401 assert s == 3, s
403 class CurrentTestCase(unittest.TestCase):
404 def test_class(self) -> None:
405 """Test the Scanner.Current class"""
406 class MyNode:
407 def __init__(self) -> None:
408 self.called_has_builder = None
409 self.called_is_up_to_date = None
410 self.func_called = None
411 def rexists(self) -> bool:
412 return True
413 class HasNoBuilder(MyNode):
414 def has_builder(self) -> bool:
415 self.called_has_builder = True
416 return False
417 class IsNotCurrent(MyNode):
418 def has_builder(self) -> bool:
419 self.called_has_builder = True
420 return True
421 def is_up_to_date(self) -> bool:
422 self.called_is_up_to_date = True
423 return False
424 class IsCurrent(MyNode):
425 def has_builder(self) -> bool:
426 self.called_has_builder = True
427 return True
428 def is_up_to_date(self) -> bool:
429 self.called_is_up_to_date = True
430 return True
431 def func(node, env, path):
432 node.func_called = True
433 return []
434 env = DummyEnvironment()
435 s = Current(func)
436 path = s.path(env)
437 hnb = HasNoBuilder()
438 s(hnb, env, path)
439 self.assertTrue(hnb.called_has_builder, "did not call has_builder()")
440 self.assertTrue(not hnb.called_is_up_to_date, "did call is_up_to_date()")
441 self.assertTrue(hnb.func_called, "did not call func()")
442 inc = IsNotCurrent()
443 s(inc, env, path)
444 self.assertTrue(inc.called_has_builder, "did not call has_builder()")
445 self.assertTrue(inc.called_is_up_to_date, "did not call is_up_to_date()")
446 self.assertTrue(not inc.func_called, "did call func()")
447 ic = IsCurrent()
448 s(ic, env, path)
449 self.assertTrue(ic.called_has_builder, "did not call has_builder()")
450 self.assertTrue(ic.called_is_up_to_date, "did not call is_up_to_date()")
451 self.assertTrue(ic.func_called, "did not call func()")
453 class ClassicTestCase(unittest.TestCase):
455 def func(self, filename, env, target, *args):
456 self.filename = filename
457 self.env = env
458 self.target = target
460 if len(args) > 0:
461 self.arg = args[0]
463 return self.deps
465 def test_find_include(self) -> None:
466 """Test the Scanner.Classic find_include() method"""
467 env = DummyEnvironment()
468 s = Classic("t", ['.suf'], 'MYPATH', r'^my_inc (\S+)')
470 def _find_file(filename, paths):
471 return paths[0]+'/'+filename
473 save = SCons.Node.FS.find_file
474 SCons.Node.FS.find_file = _find_file
476 try:
477 n, i = s.find_include('aaa', 'foo', ('path',))
478 assert n == 'foo/aaa', n
479 assert i == 'aaa', i
481 finally:
482 SCons.Node.FS.find_file = save
484 def test_name(self) -> None:
485 """Test setting the Scanner.Classic name"""
486 s = Classic("my_name", ['.s'], 'MYPATH', r'^my_inc (\S+)')
487 assert s.name == "my_name", s.name
489 def test_scan(self) -> None:
490 """Test the Scanner.Classic scan() method"""
491 class MyNode:
492 def __init__(self, name) -> None:
493 self.name = name
494 self._rfile = self
495 self.includes = None
496 def rfile(self):
497 return self._rfile
498 def exists(self):
499 return self._exists
500 def get_contents(self):
501 return self._contents
502 def get_text_contents(self):
503 return self._contents
504 def get_dir(self):
505 return self._dir
507 class MyScanner(Classic):
508 def find_include(self, include, source_dir, path):
509 return include, include
511 env = DummyEnvironment()
512 s = MyScanner("t", ['.suf'], 'MYPATH', r'^my_inc (\S+)')
514 # This set of tests is intended to test the scanning operation
515 # of the Classic scanner.
517 # Note that caching has been added for not just the includes
518 # but the entire scan call. The caching is based on the
519 # arguments, so we will fiddle with the path parameter to
520 # defeat this caching for the purposes of these tests.
522 # If the node doesn't exist, scanning turns up nothing.
523 n1 = MyNode("n1")
524 n1._exists = None
525 ret = s.function(n1, env)
526 assert ret == [], ret
528 # Verify that it finds includes from the contents.
529 n = MyNode("n")
530 n._exists = 1
531 n._dir = MyNode("n._dir")
532 n._contents = 'my_inc abc\n'
533 ret = s.function(n, env, ('foo',))
534 assert ret == ['abc'], ret
536 # Verify that it uses the cached include info.
537 n._contents = 'my_inc def\n'
538 ret = s.function(n, env, ('foo2',))
539 assert ret == ['abc'], ret
541 # Verify that if we wipe the cache, it uses the new contents.
542 n.includes = None
543 ret = s.function(n, env, ('foo3',))
544 assert ret == ['def'], ret
546 # We no longer cache overall scan results, which would be returned
547 # if individual results are de-cached. If we ever restore that
548 # functionality, this test goes back here.
549 #ret = s.function(n, env, ('foo2',))
550 #assert ret == ['abc'], 'caching inactive; got: %s'%ret
552 # Verify that it sorts what it finds.
553 n.includes = ['xyz', 'uvw']
554 ret = s.function(n, env, ('foo4',))
555 assert ret == ['uvw', 'xyz'], ret
557 # Verify that we use the rfile() node.
558 nr = MyNode("nr")
559 nr._exists = 1
560 nr._dir = MyNode("nr._dir")
561 nr.includes = ['jkl', 'mno']
562 n._rfile = nr
563 ret = s.function(n, env, ('foo5',))
564 assert ret == ['jkl', 'mno'], ret
566 def test_recursive(self) -> None:
567 """Test the Scanner.Classic class recursive flag"""
568 nodes = [1, 2, 3, 4]
571 s = Classic("Test", [], None, "", function=self.func, recursive=1)
572 n = s.recurse_nodes(nodes)
573 self.assertTrue(n == n,
574 "recursive = 1 didn't return all nodes: %s" % n)
576 def odd_only(nodes):
577 return [n for n in nodes if n % 2]
579 s = Classic("Test", [], None, "", function=self.func, recursive=odd_only)
580 n = s.recurse_nodes(nodes)
581 self.assertTrue(n == [1, 3],
582 "recursive = 1 didn't return all nodes: %s" % n)
585 class ClassicCPPTestCase(unittest.TestCase):
586 def test_find_include(self) -> None:
587 """Test the Scanner.ClassicCPP find_include() method"""
588 env = DummyEnvironment()
589 s = ClassicCPP("Test", [], None, "")
591 def _find_file(filename, paths):
592 return paths[0]+'/'+filename
594 save = SCons.Node.FS.find_file
595 SCons.Node.FS.find_file = _find_file
597 try:
598 n, i = s.find_include(('"', 'aaa'), 'foo', ('path',))
599 assert n == 'foo/aaa', n
600 assert i == 'aaa', i
602 n, i = s.find_include(('<', 'bbb'), 'foo', ('path',))
603 assert n == 'path/bbb', n
604 assert i == 'bbb', i
606 n, i = s.find_include(('<', 'ccc'), 'foo', ('path',))
607 assert n == 'path/ccc', n
608 assert i == 'ccc', i
610 finally:
611 SCons.Node.FS.find_file = save
613 def suite():
614 suite = unittest.TestSuite()
615 tclasses = [
616 FindPathDirsTestCase,
617 ScannerTestCase,
618 ScannerBaseTestCase,
619 SelectorTestCase,
620 CurrentTestCase,
621 ClassicTestCase,
622 ClassicCPPTestCase,
624 for tclass in tclasses:
625 loader = unittest.TestLoader()
626 loader.testMethodPrefix = 'test_'
627 names = loader.getTestCaseNames(tclass)
628 suite.addTests(list(map(tclass, names)))
629 return suite
631 if __name__ == "__main__":
632 TestUnit.run(suite())
634 # Local Variables:
635 # tab-width:4
636 # indent-tabs-mode:nil
637 # End:
638 # vim: set expandtab tabstop=4 shiftwidth=4: