Support the #import C preprocessor directive. (Greg Spencer)
[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 TestCmd
27 import SCons.Scanner.C
28 import unittest
29 import sys
30 import os
31 import os.path
32 import SCons.Node.FS
33 import SCons.Warnings
34
35 test = TestCmd.TestCmd(workdir = '')
36
37 os.chdir(test.workpath(''))
38
39 # create some source files and headers:
40
41 test.write('f1.cpp',"""
42 #include \"f1.h\"
43 #include <f2.h>
44
45 int main()
46 {
47    return 0;
48 }
49 """)
50
51 test.write('f2.cpp',"""
52 #include \"d1/f1.h\"
53 #include <d2/f1.h>
54 #include \"f1.h\"
55 #import <f4.h>
56
57 int main()
58 {
59    return 0;
60 }
61 """)
62
63 test.write('f3.cpp',"""
64 #include \t "f1.h"
65    \t #include "f2.h"
66 #   \t include "f3-test.h"
67
68 #include \t <d1/f1.h>
69    \t #include <d1/f2.h>
70 #   \t include <d1/f3-test.h>
71
72 // #include "never.h"
73
74 const char* x = "#include <never.h>"
75
76 int main()
77 {
78    return 0;
79 }
80 """)
81
82
83 # for Emacs -> "
84
85 test.subdir('d1', ['d1', 'd2'])
86
87 headers = ['f1.h','f2.h', 'f3-test.h', 'fi.h', 'fj.h', 'never.h',
88            'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h', 'd1/fi.h', 'd1/fj.h',
89            'd1/d2/f1.h', 'd1/d2/f2.h', 'd1/d2/f3-test.h',
90            'd1/d2/f4.h', 'd1/d2/fi.h', 'd1/d2/fj.h']
91
92 for h in headers:
93     test.write(h, " ")
94
95 test.write('f2.h',"""
96 #include "fi.h"
97 """)
98
99 test.write('f3-test.h',"""
100 #include <fj.h>
101 """)
102
103
104 test.subdir('include', 'subdir', ['subdir', 'include'])
105
106 test.write('fa.cpp',"""
107 #include \"fa.h\"
108 #include <fb.h>
109
110 int main()
111 {
112    return 0;
113 }
114 """)
115
116 test.write(['include', 'fa.h'], "\n")
117 test.write(['include', 'fb.h'], "\n")
118 test.write(['subdir', 'include', 'fa.h'], "\n")
119 test.write(['subdir', 'include', 'fb.h'], "\n")
120
121
122 test.subdir('repository', ['repository', 'include'],
123             ['repository', 'src' ])
124 test.subdir('work', ['work', 'src'])
125
126 test.write(['repository', 'include', 'iii.h'], "\n")
127
128 test.write(['work', 'src', 'fff.c'], """
129 #include <iii.h>
130 #include <jjj.h>
131
132 int main()
133 {
134     return 0;
135 }
136 """)
137
138 test.write([ 'work', 'src', 'aaa.c'], """
139 #include "bbb.h"
140
141 int main()
142 {
143    return 0;
144 }
145 """)
146
147 test.write([ 'work', 'src', 'bbb.h'], "\n")
148
149 test.write([ 'repository', 'src', 'ccc.c'], """
150 #include "ddd.h"
151
152 int main()
153 {
154    return 0;
155 }
156 """)
157
158 test.write([ 'repository', 'src', 'ddd.h'], "\n")
159
160 # define some helpers:
161
162 class DummyEnvironment:
163     def __init__(self, listCppPath):
164         self.path = listCppPath
165         
166     def Dictionary(self, *args):
167         if not args:
168             return { 'CPPPATH': self.path }
169         elif len(args) == 1 and args[0] == 'CPPPATH':
170             return self.path
171         else:
172             raise KeyError, "Dummy environment only has CPPPATH attribute."
173
174     def subst(self, arg):
175         return arg
176
177     def has_key(self, key):
178         return self.Dictionary().has_key(key)
179
180     def __getitem__(self,key):
181         return self.Dictionary()[key]
182
183     def __setitem__(self,key,value):
184         self.Dictionary()[key] = value
185
186     def __delitem__(self,key):
187         del self.Dictionary()[key]
188
189 global my_normpath
190 my_normpath = os.path.normpath
191 if os.path.normcase('foo') == os.path.normcase('FOO'):
192     global my_normpath
193     my_normpath = os.path.normcase
194
195 def deps_match(self, deps, headers):
196     scanned = map(my_normpath, map(str, deps))
197     expect = map(my_normpath, headers)
198     self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned))
199
200 def make_node(filename, fs=SCons.Node.FS.default_fs):
201     return fs.File(test.workpath(filename))
202
203 # define some tests:
204
205 class CScannerTestCase1(unittest.TestCase):
206     def runTest(self):
207         env = DummyEnvironment([])
208         s = SCons.Scanner.C.CScan()
209         path = s.path(env)
210         deps = s(make_node('f1.cpp'), env, path)
211         headers = ['f1.h', 'f2.h']
212         deps_match(self, deps, map(test.workpath, headers))
213
214 class CScannerTestCase2(unittest.TestCase):
215     def runTest(self):
216         env = DummyEnvironment([test.workpath("d1")])
217         s = SCons.Scanner.C.CScan()
218         path = s.path(env)
219         deps = s(make_node('f1.cpp'), env, path)
220         headers = ['d1/f2.h', 'f1.h']
221         deps_match(self, deps, map(test.workpath, headers))
222
223 class CScannerTestCase3(unittest.TestCase):
224     def runTest(self):
225         env = DummyEnvironment([test.workpath("d1")])
226         s = SCons.Scanner.C.CScan()
227         path = s.path(env)
228         deps = s(make_node('f2.cpp'), env, path)
229         headers = ['d1/d2/f1.h', 'd1/f1.h', 'f1.h']
230         deps_match(self, deps, map(test.workpath, headers))
231
232 class CScannerTestCase4(unittest.TestCase):
233     def runTest(self):
234         env = DummyEnvironment([test.workpath("d1"), test.workpath("d1/d2")])
235         s = SCons.Scanner.C.CScan()
236         path = s.path(env)
237         deps = s(make_node('f2.cpp'), env, path)
238         headers =  ['d1/d2/f1.h', 'd1/d2/f4.h', 'd1/f1.h', 'f1.h']
239         deps_match(self, deps, map(test.workpath, headers))
240         
241 class CScannerTestCase5(unittest.TestCase):
242     def runTest(self):
243         env = DummyEnvironment([])
244         s = SCons.Scanner.C.CScan()
245         path = s.path(env)
246
247         n = make_node('f3.cpp')
248         def my_rexists(s=n):
249             s.rexists_called = 1
250             return s.old_rexists()
251         setattr(n, 'old_rexists', n.rexists)
252         setattr(n, 'rexists', my_rexists)
253
254         deps = s(n, env, path)
255
256         # Make sure rexists() got called on the file node being
257         # scanned, essential for cooperation with BuildDir functionality.
258         assert n.rexists_called
259         
260         headers =  ['d1/f1.h', 'd1/f2.h', 'd1/f3-test.h',
261                     'f1.h', 'f2.h', 'f3-test.h']
262         deps_match(self, deps, map(test.workpath, headers))
263
264 class CScannerTestCase6(unittest.TestCase):
265     def runTest(self):
266         env1 = DummyEnvironment([test.workpath("d1")])
267         env2 = DummyEnvironment([test.workpath("d1/d2")])
268         s = SCons.Scanner.C.CScan()
269         path1 = s.path(env1)
270         path2 = s.path(env2)
271         deps1 = s(make_node('f1.cpp'), env1, path1)
272         deps2 = s(make_node('f1.cpp'), env2, path2)
273         headers1 =  ['d1/f2.h', 'f1.h']
274         headers2 =  ['d1/d2/f2.h', 'f1.h']
275         deps_match(self, deps1, map(test.workpath, headers1))
276         deps_match(self, deps2, map(test.workpath, headers2))
277
278 class CScannerTestCase8(unittest.TestCase):
279     def runTest(self):
280         fs = SCons.Node.FS.FS(test.workpath(''))
281         env = DummyEnvironment(["include"])
282         s = SCons.Scanner.C.CScan(fs = fs)
283         path = s.path(env)
284         deps1 = s(fs.File('fa.cpp'), env, path)
285         fs.chdir(fs.Dir('subdir'))
286         dir = fs.getcwd()
287         fs.chdir(fs.Dir('..'))
288         path = s.path(env, dir)
289         deps2 = s(fs.File('#fa.cpp'), env, path)
290         headers1 =  ['include/fa.h', 'include/fb.h']
291         headers2 =  ['subdir/include/fa.h', 'subdir/include/fb.h']
292         deps_match(self, deps1, headers1)
293         deps_match(self, deps2, headers2)
294
295 class CScannerTestCase9(unittest.TestCase):
296     def runTest(self):
297         SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning)
298         class TestOut:
299             def __call__(self, x):
300                 self.out = x
301
302         to = TestOut()
303         to.out = None
304         SCons.Warnings._warningOut = to
305         test.write('fa.h','\n')
306         fs = SCons.Node.FS.FS(test.workpath(''))
307         env = DummyEnvironment([])
308         s = SCons.Scanner.C.CScan(fs=fs)
309         path = s.path(env)
310         deps = s(fs.File('fa.cpp'), env, path)
311
312         # Did we catch the warning associated with not finding fb.h?
313         assert to.out
314         
315         deps_match(self, deps, [ 'fa.h' ])
316         test.unlink('fa.h')
317
318 class CScannerTestCase10(unittest.TestCase):
319     def runTest(self):
320         fs = SCons.Node.FS.FS(test.workpath(''))
321         fs.chdir(fs.Dir('include'))
322         env = DummyEnvironment([])
323         s = SCons.Scanner.C.CScan(fs=fs)
324         path = s.path(env)
325         test.write('include/fa.cpp', test.read('fa.cpp'))
326         deps = s(fs.File('#include/fa.cpp'), env, path)
327         fs.chdir(fs.Dir('..'))
328         deps_match(self, deps, [ 'include/fa.h', 'include/fb.h' ])
329         test.unlink('include/fa.cpp')
330
331 class CScannerTestCase11(unittest.TestCase):
332     def runTest(self):
333         os.chdir(test.workpath('work'))
334         fs = SCons.Node.FS.FS(test.workpath('work'))
335         fs.Repository(test.workpath('repository'))
336
337         # Create a derived file in a directory that does not exist yet.
338         # This was a bug at one time.
339         f1=fs.File('include2/jjj.h')
340         f1.builder=1
341         env = DummyEnvironment(['include', 'include2'])
342         s = SCons.Scanner.C.CScan(fs=fs)
343         path = s.path(env)
344         deps = s(fs.File('src/fff.c'), env, path)
345         deps_match(self, deps, [ test.workpath('repository/include/iii.h'), 'include2/jjj.h' ])
346         os.chdir(test.workpath(''))
347
348 class CScannerTestCase12(unittest.TestCase):
349     def runTest(self):
350         os.chdir(test.workpath('work'))
351         fs = SCons.Node.FS.FS(test.workpath('work'))
352         fs.BuildDir('build1', 'src', 1)
353         fs.BuildDir('build2', 'src', 0)
354         fs.Repository(test.workpath('repository'))
355         env = DummyEnvironment([])
356         s = SCons.Scanner.C.CScan(fs = fs)
357         path = s.path(env)
358         deps1 = s(fs.File('build1/aaa.c'), env, path)
359         deps_match(self, deps1, [ 'build1/bbb.h' ])
360         deps2 = s(fs.File('build2/aaa.c'), env, path)
361         deps_match(self, deps2, [ 'src/bbb.h' ])
362         deps3 = s(fs.File('build1/ccc.c'), env, path)
363         deps_match(self, deps3, [ 'build1/ddd.h' ])
364         deps4 = s(fs.File('build2/ccc.c'), env, path)
365         deps_match(self, deps4, [ test.workpath('repository/src/ddd.h') ])
366         os.chdir(test.workpath(''))
367
368 class CScannerTestCase13(unittest.TestCase):
369     def runTest(self):
370         class SubstEnvironment(DummyEnvironment):
371             def subst(self, arg, test=test):
372                 return test.workpath("d1")
373         env = SubstEnvironment(["blah"])
374         s = SCons.Scanner.C.CScan()
375         path = s.path(env)
376         deps = s(make_node('f1.cpp'), env, path)
377         headers = ['d1/f2.h', 'f1.h']
378         deps_match(self, deps, map(test.workpath, headers))
379         
380
381 def suite():
382     suite = unittest.TestSuite()
383     suite.addTest(CScannerTestCase1())
384     suite.addTest(CScannerTestCase2())
385     suite.addTest(CScannerTestCase3())
386     suite.addTest(CScannerTestCase4())
387     suite.addTest(CScannerTestCase5())
388     suite.addTest(CScannerTestCase6())
389     suite.addTest(CScannerTestCase8())
390     suite.addTest(CScannerTestCase9())
391     suite.addTest(CScannerTestCase10())
392     suite.addTest(CScannerTestCase11())
393     suite.addTest(CScannerTestCase12())
394     suite.addTest(CScannerTestCase13())
395     return suite
396
397 if __name__ == "__main__":
398     runner = unittest.TextTestRunner()
399     result = runner.run(suite())
400     if not result.wasSuccessful():
401         sys.exit(1)