http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / Scanner / FortranTests.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 unittest
30
31 import SCons.Scanner.Fortran
32 import SCons.Node.FS
33 import SCons.Warnings
34
35 import TestCmd
36
37 original = os.getcwd()
38
39 test = TestCmd.TestCmd(workdir = '')
40
41 os.chdir(test.workpath(''))
42
43 # create some source files and headers:
44
45 test.write('fff1.f',"""
46       PROGRAM FOO
47       INCLUDE 'f1.f'
48       include 'f2.f'
49       STOP
50       END
51 """)
52
53 test.write('fff2.f',"""
54       PROGRAM FOO
55       INCLUDE 'f2.f'
56       include 'd1/f2.f'
57       INCLUDE 'd2/f2.f'
58       STOP
59       END
60 """)
61
62 test.write('fff3.f',"""
63       PROGRAM FOO
64       INCLUDE 'f3.f' ; INCLUDE\t'd1/f3.f'
65       STOP
66       END
67 """)
68
69
70 # for Emacs -> "
71
72 test.subdir('d1', ['d1', 'd2'])
73
74 headers = ['fi.f', 'never.f',
75            'd1/f1.f', 'd1/f2.f', 'd1/f3.f', 'd1/fi.f',
76            'd1/d2/f1.f', 'd1/d2/f2.f', 'd1/d2/f3.f',
77            'd1/d2/f4.f', 'd1/d2/fi.f']
78
79 for h in headers:
80     test.write(h, "\n")
81
82
83 test.subdir('include', 'subdir', ['subdir', 'include'])
84
85 test.write('fff4.f',"""
86       PROGRAM FOO
87       INCLUDE 'f4.f'
88       STOP
89       END
90 """)
91
92 test.write('include/f4.f', "\n")
93 test.write('subdir/include/f4.f', "\n")
94
95 test.write('fff5.f',"""
96       PROGRAM FOO
97       INCLUDE 'f5.f'
98       INCLUDE 'not_there.f'
99       STOP
100       END
101 """)
102
103 test.write('f5.f', "\n")
104
105 test.subdir('repository', ['repository', 'include'],
106             [ 'repository', 'src' ])
107 test.subdir('work', ['work', 'src'])
108
109 test.write(['repository', 'include', 'iii.f'], "\n")
110
111 test.write(['work', 'src', 'fff.f'], """
112       PROGRAM FOO
113       INCLUDE 'iii.f'
114       INCLUDE 'jjj.f'
115       STOP
116       END
117 """)
118
119 test.write([ 'work', 'src', 'aaa.f'], """
120       PROGRAM FOO
121       INCLUDE 'bbb.f'
122       STOP
123       END
124 """)
125
126 test.write([ 'work', 'src', 'bbb.f'], "\n")
127
128 test.write([ 'repository', 'src', 'ccc.f'], """
129       PROGRAM FOO
130       INCLUDE 'ddd.f'
131       STOP
132       END
133 """)
134
135 test.write([ 'repository', 'src', 'ddd.f'], "\n")
136
137
138 test.write('fff90a.f90',"""
139       PROGRAM FOO
140
141 !  Test comments - these includes should NOT be picked up
142 C     INCLUDE 'fi.f'
143 #     INCLUDE 'fi.f'
144   !   INCLUDE 'fi.f'
145
146       INCLUDE 'f1.f'  ! in-line comments are valid syntax
147       INCLUDE"fi.f"   ! space is significant - this should be ignored
148       INCLUDE  <f2.f>  ! Absoft compiler allows greater than/less than delimiters
149 !
150 !  Allow kind type parameters
151       INCLUDE kindType_"f3.f"
152       INCLUDE kind_Type_"f4.f"
153 !
154 !  Test multiple statements per line - use various spacings between semicolons
155       incLUDE 'f5.f';include "f6.f"  ;  include <f7.f>; include 'f8.f' ;include kindType_'f9.f'
156 !
157 !  Test various USE statement syntaxes
158 !
159       USE Mod01
160       use mod02
161       use use
162       USE mOD03, ONLY : someVar
163       USE MOD04 ,only:someVar
164       USE Mod05 , ONLY: someVar ! in-line comment
165       USE Mod06,ONLY :someVar,someOtherVar
166
167       USE  mod07;USE  mod08; USE mod09 ;USE mod10 ; USE mod11  ! Test various semicolon placements
168       use mod12 ;use mod13! Test comment at end of line
169
170 !     USE modi
171 !     USE modia ; use modib    ! Scanner regexp will only ignore the first - this is a deficiency in the regexp
172     ! USE modic ; ! use modid  ! Scanner regexp should ignore both modules
173       USE mod14 !; USE modi    ! Only ignore the second
174       USE mod15!;USE modi
175       USE mod16  !  ;  USE  modi
176
177 !  Test semicolon syntax - use various spacings
178       USE :: mod17
179       USE::mod18
180       USE ::mod19 ; USE:: mod20
181
182       use, non_intrinsic :: mod21, ONLY : someVar ; use,intrinsic:: mod22
183       USE, NON_INTRINSIC::mod23 ; USE ,INTRINSIC ::mod24
184
185 USE mod25  ! Test USE statement at the beginning of line
186
187
188 ; USE modi   ! Scanner should ignore this since it isn't valid syntax
189       USEmodi   ! No space in between USE and module name - ignore it
190       USE mod01   ! This one is a duplicate - there should only be one dependency to it.
191
192       STOP
193       END
194 """)
195
196 modules = ['mod01.mod', 'mod02.mod', 'mod03.mod', 'mod04.mod', 'mod05.mod',
197            'mod06.mod', 'mod07.mod', 'mod08.mod', 'mod09.mod', 'mod10.mod',
198            'mod11.mod', 'mod12.mod', 'mod13.mod', 'mod14.mod', 'mod15.mod',
199            'mod16.mod', 'mod17.mod', 'mod18.mod', 'mod19.mod', 'mod20.mod',
200            'mod21.mod', 'mod22.mod', 'mod23.mod', 'mod24.mod', 'mod25.mod']
201
202 for m in modules:
203     test.write(m, "\n")
204
205 test.subdir('modules')
206 test.write(['modules', 'use.mod'], "\n")
207
208 # define some helpers:
209
210 class DummyEnvironment:
211     def __init__(self, listCppPath):
212         self.path = listCppPath
213         self.fs = SCons.Node.FS.FS(test.workpath(''))
214
215     def Dictionary(self, *args):
216         if not args:
217             return { 'FORTRANPATH': self.path, 'FORTRANMODSUFFIX' : ".mod" }
218         elif len(args) == 1 and args[0] == 'FORTRANPATH':
219             return self.path
220         else:
221             raise KeyError("Dummy environment only has FORTRANPATH attribute.")
222
223     def has_key(self, key):
224         return key in self.Dictionary()
225
226     def __getitem__(self,key):
227         return self.Dictionary()[key]
228
229     def __setitem__(self,key,value):
230         self.Dictionary()[key] = value
231
232     def __delitem__(self,key):
233         del self.Dictionary()[key]
234
235     def subst(self, arg, target=None, source=None, conv=None):
236         if arg[0] == '$':
237             return self[arg[1:]]
238         return arg
239
240     def subst_path(self, path, target=None, source=None, conv=None):
241         if not isinstance(path, list):
242             path = [path]
243         return list(map(self.subst, path))
244
245     def get_calculator(self):
246         return None
247
248     def get_factory(self, factory):
249         return factory or self.fs.File
250
251     def Dir(self, filename):
252         return self.fs.Dir(filename)
253
254     def File(self, filename):
255         return self.fs.File(filename)
256
257 def deps_match(self, deps, headers):
258     scanned = list(map(os.path.normpath, list(map(str, deps))))
259     expect = list(map(os.path.normpath, headers))
260     self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned))
261
262 # define some tests:
263
264 class FortranScannerTestCase1(unittest.TestCase):
265     def runTest(self):
266         test.write('f1.f', "\n")
267         test.write('f2.f', "      INCLUDE 'fi.f'\n")
268         env = DummyEnvironment([])
269         s = SCons.Scanner.Fortran.FortranScan()
270         path = s.path(env)
271         deps = s(env.File('fff1.f'), env, path)
272         headers = ['f1.f', 'f2.f']
273         deps_match(self, deps, headers)
274         test.unlink('f1.f')
275         test.unlink('f2.f')
276
277 class FortranScannerTestCase2(unittest.TestCase):
278     def runTest(self):
279         test.write('f1.f', "\n")
280         test.write('f2.f', "      INCLUDE 'fi.f'\n")
281         env = DummyEnvironment([test.workpath("d1")])
282         s = SCons.Scanner.Fortran.FortranScan()
283         path = s.path(env)
284         deps = s(env.File('fff1.f'), env, path)
285         headers = ['f1.f', 'f2.f']
286         deps_match(self, deps, headers)
287         test.unlink('f1.f')
288         test.unlink('f2.f')
289
290 class FortranScannerTestCase3(unittest.TestCase):
291     def runTest(self):
292         env = DummyEnvironment([test.workpath("d1")])
293         s = SCons.Scanner.Fortran.FortranScan()
294         path = s.path(env)
295         deps = s(env.File('fff1.f'), env, path)
296         headers = ['d1/f1.f', 'd1/f2.f']
297         deps_match(self, deps, headers)
298
299 class FortranScannerTestCase4(unittest.TestCase):
300     def runTest(self):
301         test.write(['d1', 'f2.f'], "      INCLUDE 'fi.f'\n")
302         env = DummyEnvironment([test.workpath("d1")])
303         s = SCons.Scanner.Fortran.FortranScan()
304         path = s.path(env)
305         deps = s(env.File('fff1.f'), env, path)
306         headers = ['d1/f1.f', 'd1/f2.f']
307         deps_match(self, deps, headers)
308         test.write(['d1', 'f2.f'], "\n")
309
310 class FortranScannerTestCase5(unittest.TestCase):
311     def runTest(self):
312         env = DummyEnvironment([test.workpath("d1")])
313         s = SCons.Scanner.Fortran.FortranScan()
314         path = s.path(env)
315         deps = s(env.File('fff2.f'), env, path)
316         headers = ['d1/f2.f', 'd1/d2/f2.f', 'd1/f2.f']
317         deps_match(self, deps, headers)
318
319 class FortranScannerTestCase6(unittest.TestCase):
320     def runTest(self):
321         test.write('f2.f', "\n")
322         env = DummyEnvironment([test.workpath("d1")])
323         s = SCons.Scanner.Fortran.FortranScan()
324         path = s.path(env)
325         deps = s(env.File('fff2.f'), env, path)
326         headers =  ['d1/f2.f', 'd1/d2/f2.f', 'f2.f']
327         deps_match(self, deps, headers)
328         test.unlink('f2.f')
329
330 class FortranScannerTestCase7(unittest.TestCase):
331     def runTest(self):
332         env = DummyEnvironment([test.workpath("d1/d2"), test.workpath("d1")])
333         s = SCons.Scanner.Fortran.FortranScan()
334         path = s.path(env)
335         deps = s(env.File('fff2.f'), env, path)
336         headers =  ['d1/f2.f', 'd1/d2/f2.f', 'd1/d2/f2.f']
337         deps_match(self, deps, headers)
338
339 class FortranScannerTestCase8(unittest.TestCase):
340     def runTest(self):
341         test.write('f2.f', "\n")
342         env = DummyEnvironment([test.workpath("d1/d2"), test.workpath("d1")])
343         s = SCons.Scanner.Fortran.FortranScan()
344         path = s.path(env)
345         deps = s(env.File('fff2.f'), env, path)
346         headers =  ['d1/f2.f', 'd1/d2/f2.f', 'f2.f']
347         deps_match(self, deps, headers)
348         test.unlink('f2.f')
349
350 class FortranScannerTestCase9(unittest.TestCase):
351     def runTest(self):
352         test.write('f3.f', "\n")
353         env = DummyEnvironment([])
354         s = SCons.Scanner.Fortran.FortranScan()
355         path = s.path(env)
356
357         n = env.File('fff3.f')
358         def my_rexists(s=n):
359             s.rexists_called = 1
360             return s.old_rexists()
361         setattr(n, 'old_rexists', n.rexists)
362         setattr(n, 'rexists', my_rexists)
363
364         deps = s(n, env, path)
365
366         # Make sure rexists() got called on the file node being
367         # scanned, essential for cooperation with VariantDir functionality.
368         assert n.rexists_called
369
370         headers =  ['d1/f3.f', 'f3.f']
371         deps_match(self, deps, headers)
372         test.unlink('f3.f')
373
374 class FortranScannerTestCase10(unittest.TestCase):
375     def runTest(self):
376         env = DummyEnvironment(["include"])
377         s = SCons.Scanner.Fortran.FortranScan()
378         path = s.path(env)
379         deps1 = s(env.File('fff4.f'), env, path)
380         env.fs.chdir(env.Dir('subdir'))
381         dir = env.fs.getcwd()
382         env.fs.chdir(env.Dir(''))
383         path = s.path(env, dir)
384         deps2 = s(env.File('#fff4.f'), env, path)
385         headers1 =  list(map(test.workpath, ['include/f4.f']))
386         headers2 =  ['include/f4.f']
387         deps_match(self, deps1, headers1)
388         deps_match(self, deps2, headers2)
389
390 class FortranScannerTestCase11(unittest.TestCase):
391     def runTest(self):
392         SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning)
393         class TestOut:
394             def __call__(self, x):
395                 self.out = x
396
397         to = TestOut()
398         to.out = None
399         SCons.Warnings._warningOut = to
400         env = DummyEnvironment([])
401         s = SCons.Scanner.Fortran.FortranScan()
402         path = s.path(env)
403         deps = s(env.File('fff5.f'), env, path)
404
405         # Did we catch the warning from not finding not_there.f?
406         assert to.out
407
408         deps_match(self, deps, [ 'f5.f' ])
409
410 class FortranScannerTestCase12(unittest.TestCase):
411     def runTest(self):
412         env = DummyEnvironment([])
413         env.fs.chdir(env.Dir('include'))
414         s = SCons.Scanner.Fortran.FortranScan()
415         path = s.path(env)
416         test.write('include/fff4.f', test.read('fff4.f'))
417         deps = s(env.File('#include/fff4.f'), env, path)
418         env.fs.chdir(env.Dir(''))
419         deps_match(self, deps, ['f4.f'])
420         test.unlink('include/fff4.f')
421
422 class FortranScannerTestCase13(unittest.TestCase):
423     def runTest(self):
424         os.chdir(test.workpath('work'))
425         fs = SCons.Node.FS.FS(test.workpath('work'))
426         fs.Repository(test.workpath('repository'))
427
428         # Create a derived file in a directory that does not exist yet.
429         # This was a bug at one time.
430         f1=fs.File('include2/jjj.f')
431         f1.builder=1
432         env = DummyEnvironment(['include','include2'])
433         env.fs = fs
434         s = SCons.Scanner.Fortran.FortranScan()
435         path = s.path(env)
436         deps = s(fs.File('src/fff.f'), env, path)
437         deps_match(self, deps, [test.workpath('repository/include/iii.f'), 'include2/jjj.f'])
438         os.chdir(test.workpath(''))
439
440 class FortranScannerTestCase14(unittest.TestCase):
441     def runTest(self):
442         os.chdir(test.workpath('work'))
443         fs = SCons.Node.FS.FS(test.workpath('work'))
444         fs.VariantDir('build1', 'src', 1)
445         fs.VariantDir('build2', 'src', 0)
446         fs.Repository(test.workpath('repository'))
447         env = DummyEnvironment([])
448         env.fs = fs
449         s = SCons.Scanner.Fortran.FortranScan()
450         path = s.path(env)
451         deps1 = s(fs.File('build1/aaa.f'), env, path)
452         deps_match(self, deps1, [ 'build1/bbb.f' ])
453         deps2 = s(fs.File('build2/aaa.f'), env, path)
454         deps_match(self, deps2, [ 'src/bbb.f' ])
455         deps3 = s(fs.File('build1/ccc.f'), env, path)
456         deps_match(self, deps3, [ 'build1/ddd.f' ])
457         deps4 = s(fs.File('build2/ccc.f'), env, path)
458         deps_match(self, deps4, [ test.workpath('repository/src/ddd.f') ])
459         os.chdir(test.workpath(''))
460
461 class FortranScannerTestCase15(unittest.TestCase):
462     def runTest(self):
463         class SubstEnvironment(DummyEnvironment):
464             def subst(self, arg, target=None, source=None, conv=None, test=test):
465                 if arg == "$junk":
466                     return test.workpath("d1")
467                 else:
468                     return arg
469         test.write(['d1', 'f2.f'], "      INCLUDE 'fi.f'\n")
470         env = SubstEnvironment(["$junk"])
471         s = SCons.Scanner.Fortran.FortranScan()
472         path = s.path(env)
473         deps = s(env.File('fff1.f'), env, path)
474         headers = ['d1/f1.f', 'd1/f2.f']
475         deps_match(self, deps, headers)
476         test.write(['d1', 'f2.f'], "\n")
477
478 class FortranScannerTestCase16(unittest.TestCase):
479     def runTest(self):
480         test.write('f1.f', "\n")
481         test.write('f2.f', "\n")
482         test.write('f3.f', "\n")
483         test.write('f4.f', "\n")
484         test.write('f5.f', "\n")
485         test.write('f6.f', "\n")
486         test.write('f7.f', "\n")
487         test.write('f8.f', "\n")
488         test.write('f9.f', "\n")
489         test.write('f10.f', "\n")
490         env = DummyEnvironment([test.workpath('modules')])
491         s = SCons.Scanner.Fortran.FortranScan()
492         path = s.path(env)
493         deps = s(env.File('fff90a.f90'), env, path)
494         headers = ['f1.f', 'f2.f', 'f3.f', 'f4.f', 'f5.f', 'f6.f', 'f7.f', 'f8.f', 'f9.f']
495         modules = ['mod01.mod', 'mod02.mod', 'mod03.mod', 'mod04.mod', 'mod05.mod',
496                    'mod06.mod', 'mod07.mod', 'mod08.mod', 'mod09.mod', 'mod10.mod',
497                    'mod11.mod', 'mod12.mod', 'mod13.mod', 'mod14.mod', 'mod15.mod',
498                    'mod16.mod', 'mod17.mod', 'mod18.mod', 'mod19.mod', 'mod20.mod',
499                    'mod21.mod', 'mod22.mod', 'mod23.mod', 'mod24.mod', 'mod25.mod', 'modules/use.mod']
500         deps_expected = headers + modules
501         deps_match(self, deps, deps_expected)
502         test.unlink('f1.f')
503         test.unlink('f2.f')
504         test.unlink('f3.f')
505         test.unlink('f4.f')
506         test.unlink('f5.f')
507         test.unlink('f6.f')
508         test.unlink('f7.f')
509         test.unlink('f8.f')
510         test.unlink('f9.f')
511         test.unlink('f10.f')
512
513 def suite():
514     suite = unittest.TestSuite()
515     suite.addTest(FortranScannerTestCase1())
516     suite.addTest(FortranScannerTestCase2())
517     suite.addTest(FortranScannerTestCase3())
518     suite.addTest(FortranScannerTestCase4())
519     suite.addTest(FortranScannerTestCase5())
520     suite.addTest(FortranScannerTestCase6())
521     suite.addTest(FortranScannerTestCase7())
522     suite.addTest(FortranScannerTestCase8())
523     suite.addTest(FortranScannerTestCase9())
524     suite.addTest(FortranScannerTestCase10())
525     suite.addTest(FortranScannerTestCase11())
526     suite.addTest(FortranScannerTestCase12())
527     suite.addTest(FortranScannerTestCase13())
528     suite.addTest(FortranScannerTestCase14())
529     suite.addTest(FortranScannerTestCase15())
530     suite.addTest(FortranScannerTestCase16())
531     return suite
532
533 if __name__ == "__main__":
534     runner = unittest.TextTestRunner()
535     result = runner.run(suite())
536     if not result.wasSuccessful():
537         sys.exit(1)
538
539 # Local Variables:
540 # tab-width:4
541 # indent-tabs-mode:nil
542 # End:
543 # vim: set expandtab tabstop=4 shiftwidth=4: