http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / Scanner / CTests.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 os
27 import os.path
28 import sys
29 import TestCmd
30 import unittest
31 import UserDict
32
33 import SCons.Node.FS
34 import SCons.Warnings
35
36 import SCons.Scanner.C
37
38 test = TestCmd.TestCmd(workdir = '')
39
40 os.chdir(test.workpath(''))
41
42 # create some source files and headers:
43
44 test.write('f1.cpp',"""
45 #include \"f1.h\"
46 #include <f2.h>
47
48 int main()
49 {
50    return 0;
51 }
52 """)
53
54 test.write('f2.cpp',"""
55 #include \"d1/f1.h\"
56 #include <d2/f1.h>
57 #include \"f1.h\"
58 #import <f4.h>
59
60 int main()
61 {
62    return 0;
63 }
64 """)
65
66 test.write('f3.cpp',"""
67 #include \t "f1.h"
68    \t #include "f2.h"
69 #   \t include "f3-test.h"
70
71 #include \t <d1/f1.h>
72    \t #include <d1/f2.h>
73 #   \t include <d1/f3-test.h>
74
75 // #include "never.h"
76
77 const char* x = "#include <never.h>"
78
79 int main()
80 {
81    return 0;
82 }
83 """)
84
85
86 # for Emacs -> "
87
88 test.subdir('d1', ['d1', 'd2'])
89
90 headers = ['f1.h','f2.h', 'f3-test.h', 'fi.h', 'fj.h', 'never.h',
91            'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h', 'd1/fi.h', 'd1/fj.h',
92            'd1/d2/f1.h', 'd1/d2/f2.h', 'd1/d2/f3-test.h',
93            'd1/d2/f4.h', 'd1/d2/fi.h', 'd1/d2/fj.h']
94
95 for h in headers:
96     test.write(h, " ")
97
98 test.write('f2.h',"""
99 #include "fi.h"
100 """)
101
102 test.write('f3-test.h',"""
103 #include <fj.h>
104 """)
105
106
107 test.subdir('include', 'subdir', ['subdir', 'include'])
108
109 test.write('fa.cpp',"""
110 #include \"fa.h\"
111 #include <fb.h>
112
113 int main()
114 {
115    return 0;
116 }
117 """)
118
119 test.write(['include', 'fa.h'], "\n")
120 test.write(['include', 'fb.h'], "\n")
121 test.write(['subdir', 'include', 'fa.h'], "\n")
122 test.write(['subdir', 'include', 'fb.h'], "\n")
123
124
125 test.subdir('repository', ['repository', 'include'],
126             ['repository', 'src' ])
127 test.subdir('work', ['work', 'src'])
128
129 test.write(['repository', 'include', 'iii.h'], "\n")
130
131 test.write(['work', 'src', 'fff.c'], """
132 #include <iii.h>
133 #include <jjj.h>
134
135 int main()
136 {
137     return 0;
138 }
139 """)
140
141 test.write([ 'work', 'src', 'aaa.c'], """
142 #include "bbb.h"
143
144 int main()
145 {
146    return 0;
147 }
148 """)
149
150 test.write([ 'work', 'src', 'bbb.h'], "\n")
151
152 test.write([ 'repository', 'src', 'ccc.c'], """
153 #include "ddd.h"
154
155 int main()
156 {
157    return 0;
158 }
159 """)
160
161 test.write([ 'repository', 'src', 'ddd.h'], "\n")
162
163 test.write('f5.c', """\
164 #include\"f5a.h\"
165 #include<f5b.h>
166 """)
167
168 test.write("f5a.h", "\n")
169 test.write("f5b.h", "\n")
170
171 # define some helpers:
172
173 class DummyEnvironment(UserDict.UserDict):
174     def __init__(self, **kw):
175         UserDict.UserDict.__init__(self)
176         self.data.update(kw)
177         self.fs = SCons.Node.FS.FS(test.workpath(''))
178
179     def Dictionary(self, *args):
180         return self.data
181
182     def subst(self, strSubst, target=None, source=None, conv=None):
183         if strSubst[0] == '$':
184             return self.data[strSubst[1:]]
185         return strSubst
186
187     def subst_list(self, strSubst, target=None, source=None, conv=None):
188         if strSubst[0] == '$':
189             return [self.data[strSubst[1:]]]
190         return [[strSubst]]
191
192     def subst_path(self, path, target=None, source=None, conv=None):
193         if not isinstance(path, list):
194             path = [path]
195         return list(map(self.subst, path))
196
197     def get_calculator(self):
198         return None
199
200     def get_factory(self, factory):
201         return factory or self.fs.File
202
203     def Dir(self, filename):
204         return self.fs.Dir(filename)
205
206     def File(self, filename):
207         return self.fs.File(filename)
208
209 if os.path.normcase('foo') == os.path.normcase('FOO'):
210     my_normpath = os.path.normcase
211 else:
212     my_normpath = os.path.normpath
213
214 def deps_match(self, deps, headers):
215     global my_normpath
216     scanned = list(map(my_normpath, list(map(str, deps))))
217     expect = list(map(my_normpath, headers))
218     self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned))
219
220 # define some tests:
221
222 class CScannerTestCase1(unittest.TestCase):
223     def runTest(self):
224         """Find local files with no CPPPATH"""
225         env = DummyEnvironment(CPPPATH=[])
226         s = SCons.Scanner.C.CScanner()
227         path = s.path(env)
228         deps = s(env.File('f1.cpp'), env, path)
229         headers = ['f1.h', 'f2.h']
230         deps_match(self, deps, headers)
231
232 class CScannerTestCase2(unittest.TestCase):
233     def runTest(self):
234         """Find a file in a CPPPATH directory"""
235         env = DummyEnvironment(CPPPATH=[test.workpath("d1")])
236         s = SCons.Scanner.C.CScanner()
237         path = s.path(env)
238         deps = s(env.File('f1.cpp'), env, path)
239         headers = ['f1.h', 'd1/f2.h']
240         deps_match(self, deps, headers)
241
242 class CScannerTestCase3(unittest.TestCase):
243     def runTest(self):
244         """Find files in explicit subdirectories, ignore missing file"""
245         env = DummyEnvironment(CPPPATH=[test.workpath("d1")])
246         s = SCons.Scanner.C.CScanner()
247         path = s.path(env)
248         deps = s(env.File('f2.cpp'), env, path)
249         headers = ['d1/f1.h', 'f1.h', 'd1/d2/f1.h']
250         deps_match(self, deps, headers)
251
252 class CScannerTestCase4(unittest.TestCase):
253     def runTest(self):
254         """Find files in explicit subdirectories"""
255         env = DummyEnvironment(CPPPATH=[test.workpath("d1"), test.workpath("d1/d2")])
256         s = SCons.Scanner.C.CScanner()
257         path = s.path(env)
258         deps = s(env.File('f2.cpp'), env, path)
259         headers =  ['d1/f1.h', 'f1.h', 'd1/d2/f1.h', 'd1/d2/f4.h']
260         deps_match(self, deps, headers)
261         
262 class CScannerTestCase5(unittest.TestCase):
263     def runTest(self):
264         """Make sure files in repositories will get scanned"""
265         env = DummyEnvironment(CPPPATH=[])
266         s = SCons.Scanner.C.CScanner()
267         path = s.path(env)
268
269         n = env.File('f3.cpp')
270         def my_rexists(s=n):
271             s.rexists_called = 1
272             return s.old_rexists()
273         setattr(n, 'old_rexists', n.rexists)
274         setattr(n, 'rexists', my_rexists)
275
276         deps = s(n, env, path)
277
278         # Make sure rexists() got called on the file node being
279         # scanned, essential for cooperation with VariantDir functionality.
280         assert n.rexists_called
281         
282         headers =  ['f1.h', 'f2.h', 'f3-test.h',
283                     'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h']
284         deps_match(self, deps, headers)
285
286 class CScannerTestCase6(unittest.TestCase):
287     def runTest(self):
288         """Find a same-named file in different directories when CPPPATH changes"""
289         env1 = DummyEnvironment(CPPPATH=[test.workpath("d1")])
290         env2 = DummyEnvironment(CPPPATH=[test.workpath("d1/d2")])
291         s = SCons.Scanner.C.CScanner()
292         path1 = s.path(env1)
293         path2 = s.path(env2)
294         deps1 = s(env1.File('f1.cpp'), env1, path1)
295         deps2 = s(env2.File('f1.cpp'), env2, path2)
296         headers1 =  ['f1.h', 'd1/f2.h']
297         headers2 =  ['f1.h', 'd1/d2/f2.h']
298         deps_match(self, deps1, headers1)
299         deps_match(self, deps2, headers2)
300
301 class CScannerTestCase8(unittest.TestCase):
302     def runTest(self):
303         """Find files in a subdirectory relative to the current directory"""
304         env = DummyEnvironment(CPPPATH=["include"])
305         s = SCons.Scanner.C.CScanner()
306         path = s.path(env)
307         deps1 = s(env.File('fa.cpp'), env, path)
308         env.fs.chdir(env.Dir('subdir'))
309         dir = env.fs.getcwd()
310         env.fs.chdir(env.Dir(''))
311         path = s.path(env, dir)
312         deps2 = s(env.File('#fa.cpp'), env, path)
313         headers1 =  list(map(test.workpath, ['include/fa.h', 'include/fb.h']))
314         headers2 =  ['include/fa.h', 'include/fb.h']
315         deps_match(self, deps1, headers1)
316         deps_match(self, deps2, headers2)
317
318 class CScannerTestCase9(unittest.TestCase):
319     def runTest(self):
320         """Generate a warning when we can't find a #included file"""
321         SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning)
322         class TestOut:
323             def __call__(self, x):
324                 self.out = x
325
326         to = TestOut()
327         to.out = None
328         SCons.Warnings._warningOut = to
329         test.write('fa.h','\n')
330         fs = SCons.Node.FS.FS(test.workpath(''))
331         env = DummyEnvironment(CPPPATH=[])
332         env.fs = fs
333         s = SCons.Scanner.C.CScanner()
334         path = s.path(env)
335         deps = s(fs.File('fa.cpp'), env, path)
336
337         # Did we catch the warning associated with not finding fb.h?
338         assert to.out
339         
340         deps_match(self, deps, [ 'fa.h' ])
341         test.unlink('fa.h')
342
343 class CScannerTestCase10(unittest.TestCase):
344     def runTest(self):
345         """Find files in the local directory when the scanned file is elsewhere"""
346         fs = SCons.Node.FS.FS(test.workpath(''))
347         fs.chdir(fs.Dir('include'))
348         env = DummyEnvironment(CPPPATH=[])
349         env.fs = fs
350         s = SCons.Scanner.C.CScanner()
351         path = s.path(env)
352         test.write('include/fa.cpp', test.read('fa.cpp'))
353         fs.chdir(fs.Dir('..'))
354         deps = s(fs.File('#include/fa.cpp'), env, path)
355         deps_match(self, deps, [ 'include/fa.h', 'include/fb.h' ])
356         test.unlink('include/fa.cpp')
357
358 class CScannerTestCase11(unittest.TestCase):
359     def runTest(self):
360         """Handle dependencies on a derived .h file in a non-existent directory"""
361         os.chdir(test.workpath('work'))
362         fs = SCons.Node.FS.FS(test.workpath('work'))
363         fs.Repository(test.workpath('repository'))
364
365         # Create a derived file in a directory that does not exist yet.
366         # This was a bug at one time.
367         f1=fs.File('include2/jjj.h')
368         f1.builder=1
369         env = DummyEnvironment(CPPPATH=['include', 'include2'])
370         env.fs = fs
371         s = SCons.Scanner.C.CScanner()
372         path = s.path(env)
373         deps = s(fs.File('src/fff.c'), env, path)
374         deps_match(self, deps, [ test.workpath('repository/include/iii.h'),
375                                  'include2/jjj.h' ])
376         os.chdir(test.workpath(''))
377
378 class CScannerTestCase12(unittest.TestCase):
379     def runTest(self):
380         """Find files in VariantDir() directories"""
381         os.chdir(test.workpath('work'))
382         fs = SCons.Node.FS.FS(test.workpath('work'))
383         fs.VariantDir('build1', 'src', 1)
384         fs.VariantDir('build2', 'src', 0)
385         fs.Repository(test.workpath('repository'))
386         env = DummyEnvironment(CPPPATH=[])
387         env.fs = fs
388         s = SCons.Scanner.C.CScanner()
389         path = s.path(env)
390         deps1 = s(fs.File('build1/aaa.c'), env, path)
391         deps_match(self, deps1, [ 'build1/bbb.h' ])
392         deps2 = s(fs.File('build2/aaa.c'), env, path)
393         deps_match(self, deps2, [ 'src/bbb.h' ])
394         deps3 = s(fs.File('build1/ccc.c'), env, path)
395         deps_match(self, deps3, [ 'build1/ddd.h' ])
396         deps4 = s(fs.File('build2/ccc.c'), env, path)
397         deps_match(self, deps4, [ test.workpath('repository/src/ddd.h') ])
398         os.chdir(test.workpath(''))
399
400 class CScannerTestCase13(unittest.TestCase):
401     def runTest(self):
402         """Find files in directories named in a substituted environment variable"""
403         class SubstEnvironment(DummyEnvironment):
404             def subst(self, arg, target=None, source=None, conv=None, test=test):
405                 if arg == "$blah":
406                     return test.workpath("d1")
407                 else:
408                     return arg
409         env = SubstEnvironment(CPPPATH=["$blah"])
410         s = SCons.Scanner.C.CScanner()
411         path = s.path(env)
412         deps = s(env.File('f1.cpp'), env, path)
413         headers = ['f1.h', 'd1/f2.h']
414         deps_match(self, deps, headers)
415
416 class CScannerTestCase14(unittest.TestCase):
417     def runTest(self):
418         """Find files when there's no space between "#include" and the name"""
419         env = DummyEnvironment(CPPPATH=[])
420         s = SCons.Scanner.C.CScanner()
421         path = s.path(env)
422         deps = s(env.File('f5.c'), env, path)
423         headers = ['f5a.h', 'f5b.h']
424         deps_match(self, deps, headers)
425
426 class CScannerTestCase15(unittest.TestCase):
427     def runTest(self):
428         """Verify scanner initialization with the suffixes in $CPPSUFFIXES"""
429         suffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
430                     ".h", ".H", ".hxx", ".hpp", ".hh",
431                     ".F", ".fpp", ".FPP",
432                     ".S", ".spp", ".SPP"]
433         env = DummyEnvironment(CPPSUFFIXES = suffixes)
434         s = SCons.Scanner.C.CScanner()
435         for suffix in suffixes:
436             assert suffix in s.get_skeys(env), "%s not in skeys" % suffix
437
438
439
440 def suite():
441     suite = unittest.TestSuite()
442     suite.addTest(CScannerTestCase1())
443     suite.addTest(CScannerTestCase2())
444     suite.addTest(CScannerTestCase3())
445     suite.addTest(CScannerTestCase4())
446     suite.addTest(CScannerTestCase5())
447     suite.addTest(CScannerTestCase6())
448     suite.addTest(CScannerTestCase8())
449     suite.addTest(CScannerTestCase9())
450     suite.addTest(CScannerTestCase10())
451     suite.addTest(CScannerTestCase11())
452     suite.addTest(CScannerTestCase12())
453     suite.addTest(CScannerTestCase13())
454     suite.addTest(CScannerTestCase14())
455     suite.addTest(CScannerTestCase15())
456     return suite
457
458 if __name__ == "__main__":
459     runner = unittest.TextTestRunner()
460     result = runner.run(suite())
461     if not result.wasSuccessful():
462         sys.exit(1)
463
464 # Local Variables:
465 # tab-width:4
466 # indent-tabs-mode:nil
467 # End:
468 # vim: set expandtab tabstop=4 shiftwidth=4: